197

我使用了 hashlib(它在 Python 2.6/3.0 中替换了 md5),如果我打开一个文件并将其内容放入hashlib.md5()函数中,它工作得很好。

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

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

4

13 回答 13

225

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

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()

注意:确保打开文件时使用“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()

上面的更新基于 Frerich Raabe 提供的评论 - 我对此进行了测试,发现它在我的 Python 2.7.2 windows 安装中是正确的

我使用“jacksum”工具交叉检查了结果。

jacksum -a md5 <filename>

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

于 2009-07-15T12:59:25.893 回答
175

将文件分成 8192 字节的块(或其他 128 字节的倍数),并使用update().

这利用了 MD5 具有 128 字节的摘要块(8192 为 128×64)这一事实。由于您没有将整个文件读入内存,因此使用的内存不会超过 8192 字节。

在 Python 3.8+ 你可以做

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
于 2009-07-15T12:55:07.827 回答
116

下面我结合了评论中的建议。谢谢你们!

蟒蛇 < 3.7

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()

Python 3.8 及更高版本

import hashlib

def checksum(filename, hash_factory=hashlib.md5, chunk_num_blocks=128):
    h = hash_factory()
    with open(filename,'rb') as f: 
        while chunk := f.read(chunk_num_blocks*h.block_size): 
            h.update(chunk)
    return h.digest()

原帖

如果您想要一种更 Pythonic(否while True)的方式来读取文件,请检查以下代码:

import hashlib

def checksum_md5(filename):
    md5 = hashlib.md5()
    with open(filename,'rb') as f: 
        for chunk in iter(lambda: f.read(8192), b''): 
            md5.update(chunk)
    return md5.digest()

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

于 2010-11-18T09:24:09.867 回答
52

这是我的@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()
于 2012-06-21T17:54:23.267 回答
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()
  • 这是“pythonic”
  • 这是一个功能
  • 它避免了隐含的值:总是更喜欢显式的值。
  • 它允许(非常重要的)性能优化

最后,

- 这是由社区建立的,感谢大家的建议/想法。

于 2013-07-22T08:14:48.793 回答
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

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

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

这里的技巧是将iter()函数与哨兵(空字符串)一起使用。

在这种情况下创建的迭代器将调用o [lambda 函数],每次调用其next()方法时不带任何参数;如果返回的值等于哨兵,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
于 2016-12-04T17:35:56.433 回答
4

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
于 2015-07-07T20:43:07.143 回答
0

如果不阅读完整内容,您将无法获得它的 md5。但是您可以使用更新功能逐块读取文件内容。
m.update(a); m.update(b) 等价于 m.update(a+b)

于 2009-07-15T12:54:22.960 回答
0

我认为下面的代码更 Pythonic:

from hashlib import md5

def get_md5(fname):
    m = md5()
    with open(fname, 'rb') as fp:
        for chunk in fp:
            m.update(chunk)
    return m.hexdigest()
于 2019-04-28T13:37:06.887 回答
0

我不喜欢循环。基于@Nathan Feger:

md5 = hashlib.md5()
with open(filename, 'rb') as f:
    functools.reduce(lambda _, c: md5.update(c), iter(lambda: f.read(md5.block_size * 128), b''), None)
md5.hexdigest()
于 2019-05-08T10:48:52.750 回答
-1

Django接受答案的实现:

import hashlib
from django.db import models


class MyModel(models.Model):
    file = models.FileField()  # any field based on django.core.files.File

    def get_hash(self):
        hash = hashlib.md5()
        for chunk in self.file.chunks(chunk_size=8192):
            hash.update(chunk)
        return hash.hexdigest()
于 2016-08-02T11:22:09.210 回答
-3
import hashlib,re
opened = open('/home/parrot/pass.txt','r')
opened = open.readlines()
for i in opened:
    strip1 = i.strip('\n')
    hash_object = hashlib.md5(strip1.encode())
    hash2 = hash_object.hexdigest()
    print hash2
于 2016-07-17T21:37:53.673 回答
-4

我不确定这里有没有太多的大惊小怪。我最近遇到了 md5 和在 MySQL 上存储为 blob 的文件的问题,所以我尝试了各种文件大小和简单的 Python 方法,即:

FileHash=hashlib.md5(FileData).hexdigest()

在 2Kb 到 20Mb 的文件大小范围内,我无法检测到明显的性能差异,因此无需“分块”散列。无论如何,如果 Linux 必须进入磁盘,它可能会做到这一点,至少与普通程序员阻止它这样做的能力一样。碰巧的是,问题与 md5 无关。如果您使用的是 MySQL,请不要忘记已经存在的 md5() 和 sha1() 函数。

于 2015-04-04T12:50:09.227 回答