相同的压缩文件在 Python 中计算出的 MD5 哈希值不一致

5

我正在尝试使用Python模块gzip压缩文件,然后使用hashlib对压缩后的文件进行哈希处理。以下是我的代码:

import hashlib
import gzip

f_name = 'read_x.fastq'

for x in range(0,3):

    file = open(f_name, 'rb')

    myzip = gzip.open('test.gz', 'wb', compresslevel=1)

    n = 100000000
    try:
        print 'zipping ' + str(x)
        for chunk in iter(lambda: file.read(n), ''):
            myzip.write(chunk)
    finally:
        file.close()
        myzip.close()

    md5 = hashlib.md5()
    print 'hashing ' + str(x)
    with open('test.gz', 'r') as f:
        for chunk in iter(lambda: f.read(n), ''):
            md5.update(chunk)

    print md5.hexdigest()
    print '\n'

我认为应该简单地压缩文件,对其进行哈希处理,并连续显示相同的输出哈希值三次。然而,我得到的输出结果是:

zipping 0
hashing 0
7bd80798bce074c65928e0cf9d66cae4


zipping 1
hashing 1
a3bd4e126e0a156c5d86df75baffc294


zipping 2
hashing 2
85812a39f388c388cb25a35c4fac87bf

如果我跳过gzip步骤,只是将同一个已经压缩的文件连续进行三次哈希,会发现输出结果确实是相同的:
hashing 0
ccfddd10c8fd1140db0b218124e7e9d3


hashing 1
ccfddd10c8fd1140db0b218124e7e9d3


hashing 2
ccfddd10c8fd1140db0b218124e7e9d3

有人能解释这里发生了什么吗?问题可能是每次gzip处理方式不同。但据我所知,DEFLATE算法是Huffman编码后跟随LZ77(一种游程长度编码)或者LZ77后跟随Huffman,因此给定相同的输入应该产生相同的输出。

2个回答

6

压缩相同内容为什么会产生不同的gzip输出呢?原因如下:

  • 压缩级别。您可以通过压缩级别参数来控制。
  • 原始文件的名称,该名称在头部中。如果您使用gzip.GzipFile api而不是gzip.open api,则可以控制此名称。
  • 修改时间也在头部中,并且可以使用gzip.GzipFile api进行控制。

因此,这里有一段代码,演示了获取Python gzip可再现输出的错误和正确方法:

import hashlib
import gzip

f_name = '/etc/passwd'
output_template = '/tmp/test{}.gz'

def digest(filename: str) -> str:
    md5 = hashlib.md5()
    with open(output_filename, 'rb') as f:
        for chunk in iter(lambda: f.read(block_size), b''):
            md5.update(chunk)
    return md5.hexdigest()

print("The default way - non identical outputs")
for x in range(0,3):
    input_handle = open(f_name, 'rb')
    output_filename = output_template.format(x)
    myzip = gzip.open(output_filename, 'wb')
    block_size = 4096
    try:
        for chunk in iter(lambda: input_handle.read(block_size), b''):
            myzip.write(chunk)
    finally:
        input_handle.close()
        myzip.close()
    print(digest(output_filename))

print("The right way to get identical outputs")
for x in range(3,6):
    input_handle = open(f_name, 'rb')
    output_filename = output_template.format(x)
    myzip = gzip.GzipFile(
        filename='',  # do not emit filename into the output gzip file
        mode='wb',
        fileobj=open(output_filename, 'wb'),
        mtime=0,
    )
    block_size = 4096
    try:
        for chunk in iter(lambda: input_handle.read(block_size), b''):
            myzip.write(chunk)
    finally:
        input_handle.close()
        myzip.close()
    print(digest(output_filename))

2

等等,看起来gzip会在gzip文件的头部添加时间戳信息,因此哈希值将不同。


1
正确。此外,不同级别的压缩、不同版本的压缩代码以及根据代码可能会给出不同编译的相同代码,这些都无关紧要。重要的是数据的解压缩能够准确还原被压缩的内容。如果你要计算某个东西的签名,应该使用输入数据而不是压缩后的数据。 - Mark Adler
2
我需要对文件进行gzip压缩,以便上传到仓库。该仓库要求检查gzipped文件的md5值,以确保传输过程中没有出现错误。因此,签名确实需要从压缩数据创建。但还是感谢您的建议。 - shaw2thefloor
这对于一次性的情况来说是可以的。我在网站上放置了我的.tar.gz分发文件的md5签名,以供验证。只是不要期望其他人压缩相同的数据会得到相同的签名。 - Mark Adler
是的,我理解了。谢谢马克。 - shaw2thefloor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接