在Python中获取大文件的MD5哈希值

Jus*_*rMe 183 python md5 hashlib

我使用了hashlib(它取代了Python 2.6/3.0中的md5),如果我打开一个文件并将其内容放入hashlib.md5()函数中,它工作正常.

问题在于非常大的文件,它们的大小可能超过RAM大小.

如何在不将整个文件加载到内存的情况下获取文件的MD5哈希值?

小智 220

您需要以合适大小的块读取文件:

def md5_for_file(f, block_size=2**20):
    md5 = hashlib.md5()
    while True:
        data = f.read(block_size)
        if not data:
            break
        md5.update(data)
    return md5.digest()
Run Code Online (Sandbox Code Playgroud)

注意:确保打开文件时将"rb"打开 - 否则会得到错误的结果.

所以要用一种方法完成所有操作 - 使用类似的东西:

def generate_file_md5(rootdir, filename, blocksize=2**20):
    m = hashlib.md5()
    with open( os.path.join(rootdir, filename) , "rb" ) as f:
        while True:
            buf = f.read(blocksize)
            if not buf:
                break
            m.update( buf )
    return m.hexdigest()
Run Code Online (Sandbox Code Playgroud)

上面的更新是基于Frerich Raabe提供的评论 - 我测试了这一点并发现它在我的Python 2.7.2 windows安装上是正确的

我使用'jacksum'工具交叉检查结果.

jacksum -a md5 <filename>
Run Code Online (Sandbox Code Playgroud)

http://www.jonelo.de/java/jacksum/

  • 需要注意的是,传递给该函数的文件必须以二进制模式打开,即通过将`rb`传递给`open`函数. (29认同)
  • 这是一个简单的补充,但使用`hexdigest`而不是`digest`将产生一个十六进制哈希,它看起来像大多数哈希的例子. (11认同)
  • Erik,不,为什么会这样?目标是将所有字节都馈送到MD5,直到文件末尾。获得部分块并不意味着不应将所有字节都馈入校验和。 (2认同)
  • @ user2084795`open` __always__会打开一个新的文件句柄,并将其位置设置为文件的开头_(除非您打开要追加的文件)。 (2认同)

Yuv*_*dam 141

将文件分成128个字节的块并连续使用它们将它们送到MD5 update().

这利用了MD5具有128字节摘要块的事实.基本上,当MD5 update()是文件时,这正是它正在做的事情.

如果确保在每次迭代时释放内存(即不将整个文件读取到内存中),则该内存不应超过128个字节.

一个例子是像这样读取块:

import hashlib
with open("your_filename.txt", "rb") as f:
    file_hash = hashlib.md5()
    while chunk := f.read(8192):
        file_hash.update(chunk)
print(file_hash.digest())
print(file_hash.hexdigest())  # to get a printable str instead of bytes
Run Code Online (Sandbox Code Playgroud)

  • 您可以有效地使用128的任意倍数(例如8192,32768等)的块大小,这将比一次读取128个字节快得多. (80认同)
  • 感谢jmanning2k这个重要的注释,对184MB文件的测试需要(0m9.230s,0m2.547s,0m2.429s)使用(128,8192,32768),我将使用8192作为更高的值给出不明显的影响. (38认同)
  • @Boris,你实际上不能说 BLAKE2 是安全的。你只能说它还没有被打破。 (4认同)

Pio*_*pla 103

如果您关心更多pythonic(没有'while True')阅读文件的方式,请检查以下代码:

import hashlib

def checksum(filename, hash_factory=hashlib.md5, chunk_num_blocks=128):
    h = hash_factory()
    with open(filename,'rb') as f: 
        for chunk in iter(lambda: f.read(chunk_num_blocks*h.block_size), b''): 
            h.update(chunk)
    return h.digest()
Run Code Online (Sandbox Code Playgroud)

请注意,iter()func需要一个空的字节字符串,以便返回的迭代器在EOF处停止,因为read()返回b''(不仅仅是'').

  • 更好的是,使用类似`128*md5.block_size`而不是'8192`的东西. (17认同)
  • `b''`语法对我来说是新的.解释[这里](http://stackoverflow.com/a/6269785/1174169). (5认同)

Nat*_*ger 49

这是我的@Piotr Czapla方法的版本:

def md5sum(filename):
    md5 = hashlib.md5()
    with open(filename, 'rb') as f:
        for chunk in iter(lambda: f.read(128 * md5.block_size), b''):
            md5.update(chunk)
    return md5.hexdigest()
Run Code Online (Sandbox Code Playgroud)


Bas*_*ene 30

在这个帖子中使用多个评论/答案,这是我的解决方案:

import hashlib
def md5_for_file(path, block_size=256*128, hr=False):
    '''
    Block size directly depends on the block size of your filesystem
    to avoid performances issues
    Here I have blocks of 4096 octets (Default NTFS)
    '''
    md5 = hashlib.md5()
    with open(path,'rb') as f: 
        for chunk in iter(lambda: f.read(block_size), b''): 
             md5.update(chunk)
    if hr:
        return md5.hexdigest()
    return md5.digest()
Run Code Online (Sandbox Code Playgroud)
  • 这是"pythonic"
  • 这是一个功能
  • 它避免了隐含的值:总是更喜欢显式值.
  • 它允许(非常重要的)性能优化

最后,

- 这是由社区建立的,感谢您的建议/想法.

  • 一个建议:使你的md5对象成为函数的可选参数,以允许备用散列函数,例如sha256,以便轻松替换MD5.我也建议将其作为编辑. (3认同)

Lau*_*RTE 9

Python 2/3便携式解决方案

要计算校验和(md5,sha1等),必须以二进制模式打开文件,因为您将对字节值求和:

要成为py27/py3便携式,您应该使用这些io包,如下所示:

import hashlib
import io


def md5sum(src):
    md5 = hashlib.md5()
    with io.open(src, mode="rb") as fd:
        content = fd.read()
        md5.update(content)
    return md5
Run Code Online (Sandbox Code Playgroud)

如果您的文件很大,您可能更喜欢按块读取文件,以避免将整个文件内容存储在内存中:

def md5sum(src, length=io.DEFAULT_BUFFER_SIZE):
    md5 = hashlib.md5()
    with io.open(src, mode="rb") as fd:
        for chunk in iter(lambda: fd.read(length), b''):
            md5.update(chunk)
    return md5
Run Code Online (Sandbox Code Playgroud)

这里的技巧是使用iter()带有sentinel的函数(空字符串).

在这种情况下创建的迭代器将调用o [lambda函数],每次调用其next()方法时都没有参数; 如果返回的值等于sentinel,StopIteration则会引发,否则返回值.

如果您的文件非常大,您可能还需要显示进度信息.您可以通过调用打印或记录计算字节数量的回调函数来实现:

def md5sum(src, callback, length=io.DEFAULT_BUFFER_SIZE):
    calculated = 0
    md5 = hashlib.md5()
    with io.open(src, mode="rb") as fd:
        for chunk in iter(lambda: fd.read(length), b''):
            md5.update(chunk)
            calculated += len(chunk)
            callback(calculated)
    return md5
Run Code Online (Sandbox Code Playgroud)


Ric*_*ard 5

Bastien Semene 代码的混音,考虑了Hawkwing关于通用哈希函数的评论......

def hash_for_file(path, algorithm=hashlib.algorithms[0], block_size=256*128, human_readable=True):
    """
    Block size directly depends on the block size of your filesystem
    to avoid performances issues
    Here I have blocks of 4096 octets (Default NTFS)

    Linux Ext4 block size
    sudo tune2fs -l /dev/sda5 | grep -i 'block size'
    > Block size:               4096

    Input:
        path: a path
        algorithm: an algorithm in hashlib.algorithms
                   ATM: ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512')
        block_size: a multiple of 128 corresponding to the block size of your filesystem
        human_readable: switch between digest() or hexdigest() output, default hexdigest()
    Output:
        hash
    """
    if algorithm not in hashlib.algorithms:
        raise NameError('The algorithm "{algorithm}" you specified is '
                        'not a member of "hashlib.algorithms"'.format(algorithm=algorithm))

    hash_algo = hashlib.new(algorithm)  # According to hashlib documentation using new()
                                        # will be slower then calling using named
                                        # constructors, ex.: hashlib.md5()
    with open(path, 'rb') as f:
        for chunk in iter(lambda: f.read(block_size), b''):
             hash_algo.update(chunk)
    if human_readable:
        file_hash = hash_algo.hexdigest()
    else:
        file_hash = hash_algo.digest()
    return file_hash
Run Code Online (Sandbox Code Playgroud)