【问题标题】:Python md5 hashes of same gzipped file are inconsistent相同gzip文件的Python md5哈希不一致
【发布时间】:2015-01-29 11:43:26
【问题描述】:

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

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 步骤,并且连续 3 次对同一个 gzip 文件进行哈希处理,我确实会得到相同的输出 3 次:

hashing 0
ccfddd10c8fd1140db0b218124e7e9d3


hashing 1
ccfddd10c8fd1140db0b218124e7e9d3


hashing 2
ccfddd10c8fd1140db0b218124e7e9d3

谁能解释这里发生了什么?问题一定是gzip过程每次都不一样。但据我所知,DEFLATE 算法是 Huffman 编码后跟 LZ77(一种行程编码形式)或 LZ77 后跟 Huffman,因此给定相同的输入应该产生相同的输出。

【问题讨论】:

    标签: python hash gzip md5


    【解决方案1】:

    压缩完全相同的内容会产生不同的 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 文件的标题中,因此哈希值会有所不同。

      【讨论】:

      • 正确。更重要的是,不同的压缩级别、不同版本的压缩代码,以及根据代码的不同,相同代码的不同编译可能会产生不同的输出。这些都不重要。重要的是数据的解压缩 可以准确地为您提供压缩的内容。如果您要计算某些东西的签名,它应该是输入数据,而不是压缩数据。
      • 好吧,我需要压缩文件以上传到存储库。 repo 需要 gzip 文件的 md5 来检查传输中没有错误。所以签名确实需要从压缩数据中创建。但感谢您的意见。
      • 一枪没问题。我将我的 .tar.gz 发行版的 md5 签名放在网站上进行验证。只是不要指望压缩相同数据的其他人会得到相同的签名。
      • 是的,我明白了。谢谢马克。
      猜你喜欢
      • 2018-03-23
      • 1970-01-01
      • 2015-02-25
      • 2015-04-13
      • 2011-05-17
      • 2017-01-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多