生成文件的MD5校验和


348

有没有简单的方法可以在Python中生成(和检查)文件列表的MD5校验和?(我正在处理一个小程序,我想确认文件的校验和)。


3
为什么不只是使用md5sum
kennytm 2010年

99
将其保留在Python中可以更轻松地管理跨平台兼容性。
亚历山大

如果你想用“进度条*或同级(对于非常大的文件)去解决,可以考虑这种解决方案:stackoverflow.com/questions/1131220/...
洛朗LAPORTE

1
@kennytm您提供的链接在第二段中表示:在描述时,“底层MD5算法不再被认为是安全的” md5sum。因此,我认为安全意识强的程序员不应使用它。
Debug255 '18

1
@ Debug255好点和有效点。双方md5sum在这太问题描述的技术,应避免-这是更好地使用SHA-2或SHA-3,如果可能的话:en.wikipedia.org/wiki/Secure_Hash_Algorithms
每伦德伯格

Answers:


462

您可以使用hashlib.md5()

请注意,有时您将无法在内存中容纳整个文件。在这种情况下,您将必须顺序读取4096个字节的块并将其提供给md5方法:

import hashlib
def md5(fname):
    hash_md5 = hashlib.md5()
    with open(fname, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()

注意: 如果只需要打包字节use ,hash_md5.hexdigest()则将返回摘要的十六进制字符串表示形式return hash_md5.digest(),因此您不必转换回去。


297

有一种方法使内存效率很低

单个文件:

import hashlib
def file_as_bytes(file):
    with file:
        return file.read()

print hashlib.md5(file_as_bytes(open(full_path, 'rb'))).hexdigest()

文件列表:

[(fname, hashlib.md5(file_as_bytes(open(fname, 'rb'))).digest()) for fname in fnamelst]

但是,请记住,MD5已知已损坏,并且不应将其用于任何目的,因为漏洞分析可能确实很棘手,并且分析代码可能用于将来的安全性问题是不可能的。恕我直言,应该从库中将其完全删除,以便使用它的每个人都必须进行更新。因此,这是您应该做的:

[(fname, hashlib.sha256(file_as_bytes(open(fname, 'rb'))).digest()) for fname in fnamelst]

如果只需要128位摘要,则可以执行.digest()[:16]

这将为您提供一个元组列表,每个元组都包含其文件名和哈希值。

我再次强烈质疑您对MD5的使用。您至少应该使用SHA1,并且鉴于SHA1中发现的最新缺陷,可能甚至没有。有人认为,只要您不将MD5用于“加密”目的,就可以了。但是,事情的范围最终趋向于超出您最初的预期,并且偶然的漏洞分析可能证明是完全有缺陷的。最好只是养成使用正确算法的习惯。只是输入了不同的字母而已。没那么难。

这是一种更复杂但内存有效的方法

import hashlib

def hash_bytestr_iter(bytesiter, hasher, ashexstr=False):
    for block in bytesiter:
        hasher.update(block)
    return hasher.hexdigest() if ashexstr else hasher.digest()

def file_as_blockiter(afile, blocksize=65536):
    with afile:
        block = afile.read(blocksize)
        while len(block) > 0:
            yield block
            block = afile.read(blocksize)


[(fname, hash_bytestr_iter(file_as_blockiter(open(fname, 'rb')), hashlib.md5()))
    for fname in fnamelst]

再说一次,由于MD5损坏了,不再应该再使用了:

[(fname, hash_bytestr_iter(file_as_blockiter(open(fname, 'rb')), hashlib.sha256()))
    for fname in fnamelst]

同样,如果只需要128位摘要[:16]hash_bytestr_iter(...)则可以在调用之后放置。


66
我仅使用MD5确认文件未损坏。我不太担心它被破坏了。
亚历山大

87
@TheLifelessOne:尽管@Omnifarious发出了可怕的警告,但这完全是对MD5的很好使用。
总统詹姆斯·波尔克(James K. Polk)2010年

22
@ GregS,@ TheLifelessOne-是的,接下来您知道有人找到一种方法来使用有关您的应用程序的事实,以使该文件在根本不是您期望的文件时被视为未损坏。不,我坚持我的可怕警告。我认为应删除MD5或附带弃用警告。
2010年

10
我可能会使用.hexdigest()而不是.digest()-这对于人类来说更容易阅读-这是OP的目的。
zbstof

21
我使用了这种解决方案,但是对于两个不同的pdf文件,它错误地给出了相同的哈希值。解决方案是通过指定二进制模式打开文件,即:[[fname,hashlib.md5(open(fname,'rb').read())。hexdigest())for fnamelst中的fname]到比md5更大的开放函数,但是考虑到上述跨平台兼容性的要求,我认为报告它可能会很有用(另请参阅:docs.python.org/2/tutorial/…)。
BlueCoder

34

我显然没有添加任何根本上没有新的内容,而是在我要评论状态之前添加了此答案,并且代码区域使事情更加清晰了-无论如何,特别是要从Omnifarious的答案中回答@Nemo的问题:

我碰巧在考虑校验和(特别是在这里寻找有关块大小的建议),并且发现此方法可能比您期望的要快。以最快的(但相当典型值)timeit.timeit/usr/bin/time从每个执行校验和的约文件的几种方法的结果。11MB:

$ ./sum_methods.py
crc32_mmap(filename) 0.0241742134094
crc32_read(filename) 0.0219960212708
subprocess.check_output(['cksum', filename]) 0.0553209781647
md5sum_mmap(filename) 0.0286180973053
md5sum_read(filename) 0.0311000347137
subprocess.check_output(['md5sum', filename]) 0.0332629680634
$ time md5sum /tmp/test.data.300k
d3fe3d5d4c2460b5daacc30c6efbc77f  /tmp/test.data.300k

real    0m0.043s
user    0m0.032s
sys     0m0.010s
$ stat -c '%s' /tmp/test.data.300k
11890400

因此,对于11MB的文件来说,Python和/ usr / bin / md5sum大约都需要30毫秒。相关md5sum功能(md5sum_read在上面的清单中)与Omnifarious的功能非常相似:

import hashlib
def md5sum(filename, blocksize=65536):
    hash = hashlib.md5()
    with open(filename, "rb") as f:
        for block in iter(lambda: f.read(blocksize), b""):
            hash.update(block)
    return hash.hexdigest()

当然,这些都是单次运行的(mmap至少进行几十次运行时,总是总是更快一些),并且f.read(blocksize)在缓冲区用完后,我的通常会获得额外的收入,但是它是相当可重复的,并且md5sum在命令行上显示不一定比Python实现要快...

编辑:抱歉,很长的延迟,已经有一段时间没有看到了,但是为了回答@EdRandall的问题,我将写下一个Adler32实现。但是,我还没有运行基准测试。它基本上与CRC32相同:除了初始化,更新和摘要调用外,其他所有操作都是zlib.adler32()调用:

import zlib
def adler32sum(filename, blocksize=65536):
    checksum = zlib.adler32("")
    with open(filename, "rb") as f:
        for block in iter(lambda: f.read(blocksize), b""):
            checksum = zlib.adler32(block, checksum)
    return checksum & 0xffffffff

请注意,这必须与空字符串从零对他们的总和启动时开始,随着阿德勒资金做的确有所不同"",这是1- CRC可以开始0代替。AND需要使用-ing使其成为32位无符号整数,以确保其在Python版本之间返回相同的值。


您是否可以添加一些比较SHA1的行,也许还有zlib.adler32?
艾德·兰德尔

1
上面的md5sum()函数假定您具有对该文件的写访问权。如果将open()调用中的“ r + b”替换为“ rb”,它将正常工作。
凯文·利达

1
@EdRandall:adler32确实不值得打扰,例如。leviathansecurity.com/blog/analysis-of-adler32
MikeW '16

6

在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

考虑使用hashlib.blake2b而不是md5(只需在上面的代码段中替换md5blake2b)。它的加密安全性比MD5 更快


:=运算符是“赋值运算符”(Python 3.8+中的新增功能);它允许您在较大的表达式内分配值;此处的更多信息:docs.python.org/3/whatsnew/3.8.html#assignment-expressions
本杰明

0
hashlib.md5(pathlib.Path('path/to/file').read_bytes()).hexdigest()

3
嗨!请向您的代码添加一些解释,以说明为什么这是解决问题的方法。此外,这篇文章已经很老了,因此您还应该添加一些信息,说明您的解决方案为何添加了其他解决方案尚未解决的问题。
d_kennetz

1
这是另一种内存效率低下的方式
Erik Aronesty,

-2

我认为依靠invoke包和md5sum二进制文件比子进程或md5包更方便

import invoke

def get_file_hash(path):

    return invoke.Context().run("md5sum {}".format(path), hide=True).stdout.split(" ")[0]

当然,这假定您已经安装了invoke和md5sum。


3
如果path是用户提供的路径,这将允许任何用户在系统上执行任意bash命令。
鲍里斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.