生成文件的MD5校验和

467

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


4
为什么不直接使用md5sum呢? - kennytm
119
在Python中保留它可以更轻松地管理跨平台兼容性。 - Alexander
2
@kennytm,您提供的链接在第二段中说:“底层MD5算法不再被认为是安全的”,同时描述了md5sum。这就是为什么有安全意识的程序员在我看来不应该使用它的原因。 - Debug255
1
@Debug255 很好的观点。应该避免使用md5sum和这个SO问题中描述的技术 - 最好使用SHA-2或SHA-3,如果可能的话:https://en.wikipedia.org/wiki/Secure_Hash_Algorithms - Per Lundberg
3
值得一提的是,仍然存在使用MD5的有效原因,这些原因与其安全性问题无关(例如,在使用内置MD5创建进行归档的系统中检查位腐败)。 - Smock
显示剩余5条评论
9个回答

655

你可以使用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()

注意: hash_md5.hexdigest() 将返回摘要的十六进制字符串表示形式,如果您只需要打包的字节,请使用 return hash_md5.digest(),这样您不必进行转换。


我该如何解码十六进制字符串?它与“md5sum”返回的输出不同。 - alper
@alper 不,它不会 — 抱歉听起来这么轻率,但是对于相同的输入,MD5 没有不同之处 — 如果您正在读取二进制(而不是在意行终止符的)输入,则该算法是确定性的 — MD5 的著名问题是可能会出现两个不同的输入_无法区分_。 - rsandwick3
据我所知,MD5公式可能会生成两个不同输入的相同输出? - alper
2
请问是否有两个已知的字符串具有相同的MD5哈希值? - rsandwick3

333

有一种方法非常费内存。

单文件:

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 位的摘要,可以在调用 hash_bytestr_iter(...) 后加上 [:16]


79
我只是使用MD5来确认文件未被损坏,对其是否已经破解并不太担心。 - Alexander
97
尽管 @Omnifarious 发出了可怕的警告,但这仍然是 MD5 的完全合理应用。 - President James K. Polk
24
@GregS, @TheLifelessOne - 是的,接下来可能会有人找到一种方法利用你的应用程序这个事实,使得一个不是你所期望的文件被接受为未损坏的文件。不,我仍然坚持我的可怕警告。我认为MD5应该被移除或附带弃用警告。 - Omnifarious
25
我使用了这个解决方案,但它错误地为两个不同的PDF文件生成了相同的哈希值。 解决方案是通过指定二进制模式打开文件,即:[(fname, hashlib.md5(open(fname, 'rb').read()).hexdigest()) for fname in fnamelst]这更与open函数相关,而不是md5,但考虑到上面提到的跨平台兼容性要求(另请参见:http://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files),我认为报告这个问题可能很有用。 - BlueCoder
10
说“从Python库中删除MD5”甚至只是说“向Python库添加弃用警告”就像在说“不应该使用Python,如果现有的内容需要MD5,请使用其他东西”。可以在文档中解释安全影响,但是删除甚至仅仅是弃用是一个荒谬的建议。 - hyde
显示剩余31条评论

36

在我还没有达到评论状态之前,我显然没有添加任何根本上的新东西,但是为了回答@Nemo关于Omnifarious答案的问题,我已经添加了这个答案,而且代码区域使事情更加清晰--无论如何:

我碰巧在思考一些校验和方面的问题(特别是在寻找有关块大小建议的建议时来到这里),并发现这种方法可能比您预期的要快。从几种校验文件大小约为11MB的文件的最快(但相当典型的)timeit.timeit/usr/bin/time结果中选取:

$ ./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

看起来 Python 和 /usr/bin/md5sum 都需要大约 30 毫秒来处理一个 11MB 的文件。相关的 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
请注意,这必须从空字符串开始,因为 Adler 总和从零开始与其在 "" 的总和确实不同,后者为 1-- CRC 可以以 0 开头。需要使用 AND 运算使其成为一个32位无符号整数,这可以确保在 Python 版本之间返回相同的值。

你能否添加几行代码来比较SHA1和zlib.adler32呢? - Ed Randall
1
@EdRandall:adler32真的不值得费心,例如:http://www.leviathansecurity.com/blog/analysis-of-adler32 - MikeW

35
在 Python 3.8+ 中,你可以像这样使用 赋值运算符 :=(与hashlib一起使用)。
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(只需在上述代码段中将 md5 替换为 blake2b)。它是加密安全的,比 MD5 更快速


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

5
你好!请在你的代码中加入一些解释,说明为什么这是解决问题的方案。此外,这篇文章已经很旧了,因此你还应该添加一些信息,说明你的解决方案为什么具有其他人尚未解决的问题。 - d_kennetz
6
这是一种内存效率低下的方法。 - Erik Aronesty
2
一行代码解决方案。非常适合进行一些测试! - breakthewall

10
在Python 3.11+中,有一种新的可读性和内存效率更高的方法:hashlib.file_digest
import hashlib
with open(path, "rb") as f:
    digest = hashlib.file_digest(f, "md5")
print(digest.hexdigest())

3
您可以使用 simple-file-checksum1,它仅使用subprocess调用 openssl(macOS/Linux)或 CertUtil(Windows),并从输出中仅提取摘要。

安装:

pip install simple-file-checksum

使用方法:

>>> from simple_file_checksum import get_checksum
>>> get_checksum("path/to/file.txt")
'9e107d9d372bb6826bd81d3542a419d6'
>>> get_checksum("path/to/file.txt", algorithm="MD5")
'9e107d9d372bb6826bd81d3542a419d6'

支持使用SHA1SHA256SHA384SHA512算法。


1披露:我是simple-file-checksum的作者。


0

你可以在这里使用 shell。

from subprocess import check_output

#for windows & linux
hash = check_output(args='md5sum imp_file.txt', shell=True).decode().split(' ')[0]

#for mac
hash = check_output(args='md5 imp_file.txt', shell=True).decode().split('=')[1]

-1

file_path更改为您的文件路径

import hashlib
def getMd5(file_path):
    m = hashlib.md5()
    with open(file_path,'rb') as f:
        lines = f.read()
        m.update(lines)
    md5code = m.hexdigest()
    return md5code

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接