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

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

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

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

145281 次浏览

如果不阅读完整的内容,就无法获得它的md5。但是你可以使用update函数逐块读取文件的内容。

m.update(一个);m.update (b)等价于m.update (a + b)

将文件分解为8192字节的块(或其他128字节的倍数),并使用update()连续将它们馈送给MD5。

这利用了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

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

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>

Python & lt;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''(而不仅仅是'')。

下面是我的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()

使用多个评论/回答这个问题,以下是我的解决方案:

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()
  • 这是神谕的
  • 这是一个函数
  • 它避免隐式值:总是偏爱显式值。
  • 它允许(非常重要的)性能优化

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

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

在2 KB到20 MB的文件大小范围内,我没有检测到任何明显的性能差异,因此不需要“块”散列。无论如何,如果Linux必须转到磁盘上,它可能至少会做得和普通程序员避免这样做的能力一样好。碰巧的是,这个问题与md5无关。如果您正在使用MySQL,不要忘记已经存在的md5()和sha1()函数。

巴斯蒂安·塞梅尼的密码的一个混音,它考虑了关于泛型哈希函数的霍克因的评论

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

尤瓦尔·亚当的回答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()

Python 2/3可移植解决方案

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

要使Python 2.7和Python 3可移植,你应该使用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()方法时不带参数;如果返回值等于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

我认为下面的代码更神谕的:

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

我不喜欢循环。基于内森·费格的回答:

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