如何创建在python中为相同内容保留相同md5哈希的存档?

时间:2017-07-11 13:20:16

标签: python-2.7 gzip tar md5sum

正如本文https://medium.com/@mpreziuso/is-gzip-deterministic-26c81bfd0a49中解释的那样,压缩完全相同文件集的两个.tar.gz文件的md5可能不同。这是因为它例如在压缩文件的标题中包含时间戳。

在文章3中提出了解决方案,我希望能够使用第一个解决方案:

  

我们可以在gzip中使用-n标志,这将使gzip省略文件头中的时间戳和文件名;

此解决方案效果很好:

tar -c ./bin |gzip -n >one.tar.gz
tar -c ./bin |gzip -n >two.tar.gz
md5sum one.tgz two.tgz

然而我不知道在python中做什么是好方法。 有没有办法用tarfile(https://docs.python.org/2/library/tarfile.html)?

4 个答案:

答案 0 :(得分:1)

作为一种解决方法,您可以使用bzip2压缩。它似乎没有这个问题:

import tarfile

tar1 = tarfile.open("one.tar.bz2", "w:bz2")
tar1.add("bin")
tar1.close()

tar2 = tarfile.open("two.tar.bz2", "w:bz2")
tar2.add("bin")
tar2.close()

运行md5会给出:

martin@martin-UX305UA:~/test$ md5sum one.tar.bz2 two.tar.bz2 
e9ec2fd4fbdfae465d43b2f5ecaecd2f  one.tar.bz2
e9ec2fd4fbdfae465d43b2f5ecaecd2f  two.tar.bz2

答案 1 :(得分:1)

Martin's answer是正确的,但就我而言,我也想忽略tar中每个文件的最后修改日期,因此,即使文件已被“修改”但没有实际更改,它仍然具有相同的哈希值。

创建tar时,我可以覆盖不需要的值,因此它们始终相同。

在此示例中,我显示仅使用普通的tar.bz2,如果我使用新的创建时间戳重新创建源文件,则哈希将更改(1和2相同,重新创建后,4将不同)。但是,如果将时间设置为Unix Epoch 0(或任何其他任意时间),则我的文件将全部散列为相同的值(3、5和6)

为此,您需要向filter传递一个tar.add(DIR, filter=tarInfoStripFileAttrs)函数,以删除所需的字段,如下面的示例所示

import tarfile, time, os

def createTestFile():
    with open(DIR + "/someFile.txt", "w") as file:
        file.write("test file")

# Takes in a TarInfo and returns the modified TarInfo:
# https://docs.python.org/3/library/tarfile.html#tarinfo-objects
# intented to be passed as a filter to tarfile.add
# https://docs.python.org/3/library/tarfile.html#tarfile.TarFile.add
def tarInfoStripFileAttrs(tarInfo):
    # set time to epoch timestamp 0, aka 00:00:00 UTC on 1 January 1970
    # note that when extracting this tarfile, this time will be shown as the modified date
    tarInfo.mtime = 0

    # file permissions, probably don't want to remove this, but for some use cases you could
    # tarInfo.mode = 0

    # user/group info
    tarInfo.uid= 0
    tarInfo.uname = ''
    tarInfo.gid= 0
    tarInfo.gname = ''

    # stripping paxheaders may not be required
    # see https://stackoverflow.com/questions/34688392/paxheaders-in-tarball
    tarInfo.pax_headers = {}

    return tarInfo


# COMPRESSION_TYPE = "gz" # does not work even with filter
COMPRESSION_TYPE = "bz2"
DIR = "toTar"
if not os.path.exists(DIR):
    os.mkdir(DIR)

createTestFile()

tar1 = tarfile.open("one.tar." + COMPRESSION_TYPE, "w:" + COMPRESSION_TYPE)
tar1.add(DIR)
tar1.close()

tar2 = tarfile.open("two.tar." + COMPRESSION_TYPE, "w:" + COMPRESSION_TYPE)
tar2.add(DIR)
tar2.close()

tar3 = tarfile.open("three.tar." + COMPRESSION_TYPE, "w:" + COMPRESSION_TYPE)
tar3.add(DIR, filter=tarInfoStripFileAttrs)
tar3.close()

# Overwrite the file with the same content, but an updated time
time.sleep(1)
createTestFile()

tar4 = tarfile.open("four.tar." + COMPRESSION_TYPE, "w:" + COMPRESSION_TYPE)
tar4.add(DIR)
tar4.close()


tar5 = tarfile.open("five.tar." + COMPRESSION_TYPE, "w:" + COMPRESSION_TYPE)
tar5.add(DIR, filter=tarInfoStripFileAttrs)
tar5.close()

tar6 = tarfile.open("six.tar." + COMPRESSION_TYPE, "w:" + COMPRESSION_TYPE)
tar6.add(DIR, filter=tarInfoStripFileAttrs)
tar6.close()
$ md5sum one.tar.bz2 two.tar.bz2 three.tar.bz2 four.tar.bz2 five.tar.bz2 six.tar.bz2
0e51c97a8810e45b78baeb1677c3f946  one.tar.bz2      # same as 2
0e51c97a8810e45b78baeb1677c3f946  two.tar.bz2      # same as 1
54a38d35d48d4aa1bd68e12cf7aee511  three.tar.bz2    # same as 5/6
22cf1161897377eefaa5ba89e3fa6acd  four.tar.bz2     # would be same as 1/2, but timestamp has changed
54a38d35d48d4aa1bd68e12cf7aee511  five.tar.bz2     # same as 3, even though timestamp has changed
54a38d35d48d4aa1bd68e12cf7aee511  six.tar.bz2      # same as 3, even though timestamp has changed

您可能要根据用例来调整要修改的参数以及过滤器函数的功能。

答案 2 :(得分:1)

我需要将许多文件归档到一个 tar 文件(不仅仅是一个)中,而上述答案对我不起作用。相反,我将 Linux tar 命令与 Python 的 subprocess 模块一起使用:

import subprocess
import shlex 

def make_tarfile_linux(folder_path, filename):
    """
    Make idempotent tarfile for an identical checksum each time.
    However, this method does not filter out unwanted files like Python can...
    """
    tarfile_to_create_path_and_filename = f"/home/user/{filename}"
    tar_command = "tar --sort=name --owner=root:0 --group=root:0 --mtime='UTC 1970-01-01' -cjf"
    command_list = shlex.split(f"{tar_command} {tarfile_to_create_path_and_filename} {folder_path}")
    cp = subprocess.run(command_list)

    return None

答案 3 :(得分:0)

当然,您可以消除 tar 和 gzip 标头中的日期和其他非文件信息,并使用具有相同设置的相同压缩器的相同版本,所有这些都是为了获得完全相同的存档字节。

然而,这样做让我认为您解决了错误的问题,如果有人更改了您的压缩器版本,并且版本更改前后的签名不匹配,您就会遇到问题。< /p>

我建议您使用未压缩文件内容的串联来生成您的签名。那么您的签名将自然而然地独立于您当前必须进行一定长度以归零的所有内容,并且也将独立于压缩代码的更改。然后,您需要做的就是注意保留存档中文件的顺序。