Generating an MD5 checksum of a file
在python中,是否有生成(和检查)文件列表的MD5校验和的简单方法?(我正在处理一个小程序,我想确认文件的校验和)。
- 为什么不直接使用md5sum?
- 将它保存在python中可以更容易地管理跨平台的兼容性。
- 如果您希望使用"进度条"或类似的DE解决方案(对于非常大的文件),请考虑以下解决方案:stackoverflow.com/questions/1131220/…
- @你提供的链接kennytm在第二段中说:"在描述md5sum时,底层MD5算法不再被认为是安全的"。这就是为什么我认为有安全意识的程序员不应该使用它的原因。
- @DEBUG255良好有效点。应避免使用md5sum和本问题中描述的技术——如果可能,最好使用sha-2或sha-3:en.wikipedia.org/wiki/secure_hash_算法
您可以使用hashlib.md5()。
请注意,有时您将无法在内存中容纳整个文件。在这种情况下,您必须按顺序读取4096字节的块,并将它们馈送给MD5函数:
1 2 3 4 5 6
| 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(),因此不必转换回。
- 如果任何一个文件大于1MB,那么我有一些问题。不过谢谢。我想这解决了我的问题。
- 为什么大于1MB的文件会有问题?
- @马丁托马-那是7.5年前写的,乘以32左右。
- @Martinthoma,我把操作读为"我知道我将处理的所有文件都将小于1 MB,所以如果其中任何一个文件超过1 MB,那么就会出现问题。"
- 我使用hashlib.md5()为两个不同的XML文件获取相同的文件哈希。不过,使用您的解决方案,现在它工作得很好。谢谢:)
有一种方法内存效率很低。
单个文件:
1 2 3 4 5 6
| import hashlib
def file_as_bytes(file):
with file:
return file.read()
print hashlib.md5(file_as_bytes(open(full_path, 'rb'))).hexdigest() |
文件列表:
1
| [(fname, hashlib.md5(file_as_bytes(open(fname, 'rb'))).digest()) for fname in fnamelst] |
号
不过,请记住,MD5已知已损坏,不应用于任何目的,因为漏洞分析可能非常棘手,并且不可能分析将来可能用于安全问题的代码。imho,它应该从库中完全删除,这样所有使用它的人都必须更新。所以,你应该做的是:
1
| [(fname, hashlib.sha256(file_as_bytes(open(fname, 'rb'))).digest()) for fname in fnamelst] |
如果您只需要128位的摘要,可以执行.digest()[:16]。
这将为您提供一个元组列表,每个元组包含其文件名及其哈希。
我再次强烈质疑你对MD5的使用。你至少应该使用sha1,考虑到最近在sha1中发现的缺陷,甚至可能不是这样。有些人认为,只要您不使用MD5进行"加密"目的,就可以了。但是,事情有一个趋势,最终会比你最初预期的范围更广,你的随意的脆弱性分析可能会被证明是完全错误的。最好是养成在大门外使用正确算法的习惯。只是打了一堆不同的信而已。这并不难。
以下是一种更复杂但内存效率更高的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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已损坏,因此不应再使用:
1 2
| [(fname, hash_bytestr_iter(file_as_blockiter(open(fname, 'rb')), hashlib.sha256()))
for fname in fnamelst] |
同样,如果您只需要128位的摘要,那么可以在调用hash_bytestr_iter(...)之后放入[:16]。
- 我只是使用MD5来确认文件没有损坏。我不担心它会坏掉。
- @Lifelessone:尽管有@Omnifarious可怕的警告,但这是MD5的完美使用。
- @格雷格,@thelifesone-是的,下一件事你知道,有人会利用你的申请中的这个事实,使一个文件在它不是你所期待的文件时被接受为未损坏的。不,我遵守我可怕的警告。我认为MD5应该被移除,或者带有贬低警告。
- 虽然@quantumsoup有一个可行的答案,但我认为应该选择这个方法作为检索文件md5校验和的正确方法。但是,可以将其简化为"hashlib.md5(open(fname,'r').read()).digest()"。应该注意,"file"函数已更改为"open",以便与python 2.7一起使用。+
- @奥斯汀:点头。我把它改成了"以东十一号"(0)。我相信自Hashlib被引入以来,这一直都是有效的,而且可能一直都是有效的。旧习惯很难改掉。
- 我可能会使用.hexDigest()而不是.digest()——这对人类来说更容易阅读——这就是op的目的。
- @佐托夫:我会从标准的hashlib散列函数接口中删除hexdigest。我觉得这是不必要的疣。我喜欢让小功能广泛应用。在许多情况下,散列的十六进制非常不必要地冗长,使得最容易使用的版本鼓励人们在不必要时冗长。但是的,在这种情况下,为了这个特定的目的,它可能是更好的选择。我还是用binascii.hexlify来代替。-)
- 我使用了这个解决方案,但它错误地给出了两个不同PDF文件的相同哈希值。解决方案是通过指定二进制模式打开文件,即:对于fnamelst中的fname,为其指定:【fname,hashlib.md5(open(fname,'rb').read()).hexDigest()】,这与open函数比md5更相关,但我认为考虑到上述跨平台兼容性的要求,报告它可能会有用(另请参见:docs.python.org/2/教程/…)。
- @哦,你说得对。我应该这么做。我很习惯用Unix,因为这两者是同义的。我现在就修。
- @无所不知的说法"从python库中删除md5",甚至只是说"向python库添加拒绝警告",就像是说"不应该使用python,如果现有的东西需要md5,请使用其他东西"。当然,在文档中解释安全含义,但删除甚至只是贬低都是愚蠢的建议。
- @海德:必须采取措施让人们停止使用这种愚蠢的算法。我做过一些工作,他们坚持使用它,甚至在我证明它创建了安全漏洞(公认是相当模糊的漏洞)并且sha在openssl(我们正在使用的库)中实现得更快之后。太疯狂了。
- 在命令行中,有没有任何方法可以使它比MD5SUM慢一个数量级?
- 对于在同一个文件句柄上多次使用上述def hashfile功能的用户,在读取每个文件时,请记住重置afile指针。如afile.seek(0)。
- 提醒:MD5的已知弱点是冲突攻击,而不是预映像攻击,因此它适用于某些加密应用程序,但不适用于其他应用程序。如果你不知道区别,你就不应该使用它,但不要完全放弃它。请参见vpnc.org/hash.html。
- 不关闭这些列表理解中打开的文件可以吗?
- 是的,我也想问同样的问题。这里没有close()吗?
- 不,不好。文件将在垃圾收集时关闭,可能在封闭函数的末尾。例如,如果fnamelist中的元素数量大于操作系统设置的限制,那么它将失败。但这与问题无关。我们应该使用so来获取要点,而不是盲目地复制片段。:)
- @BlueCoder两个不同的PDF文件有相同的哈希值是如何发生的,即使打开时没有使用mode=rb?难道rt不应该简单地转换换行符,否则就等同于rb吗?(我假设这是python 2,因为在python 3中,hashlib.md5需要bytes并且只会拒绝接受字符串,)
- @Romanshapovalov-i依赖于引用计数的python对象的性质。在对列表理解的每个元素进行评估之后,就不再有对它的引用了。我同意这是相当脆弱的,过于依赖于执行。:-/i喜欢hashfile的接口,但是它更灵活,因为它可以处理任何有read的东西。
- @Romanshapovalov-我修复了它,这样它就不再有潜在的资源泄漏,即使当前的cpython实现没有。我同意它应该避免泄漏,即使是在jython或将来可能的cpython实现上。
- @杰森-我可以把我的手放在液氮里,这样不会受到伤害。这并不意味着我应该这么做。有许多MD5的替代品可供广泛使用。任何人都没有理由使用它,就像我把手伸进液氮一样。
- 不。对不起的。不好的类比。
- @Jasons-你能给出一个合理的理由吗?任何人都应该使用MD5,而不是这两种情况中的一种:"好吧,我认为在这种情况下我可以摆脱它。"或者"我必须与其他使用MD5的东西进行互操作"。
- 整个生命是关于"我认为在这种情况下我可以摆脱它"——或者更客观地说,风险管理,它适用于所有密码系统,包括MD5和SHA1。阅读有关MD5预映像攻击的最新技术。我不会在家里的所有窗户上都安装栅栏,在没有恶意对手的情况下(例如,将文件从一台电脑复制到另一台电脑),我会使用MD5进行花园式完整性检查。
- web.archive.org/web/20150901084550/http://www.vpnc.org/…--"碰撞攻击和两种图像前攻击之间的区别至关重要。在撰写本文时,没有实际的预映像攻击,这意味着如果您使用的哈希仅易受预映像攻击,那么即使MD5也很好,因为AT攻击者必须做出2^128次猜测,这在几十年内(如果有)都是不可行的。"
- @Jasons——这样做的话,你就是在延续一种算法的使用和存在,这种算法已经被广泛的其他用途打破了。使用正确的算法不像在窗户上加横条。使用正确的算法是以不同的方式键入几个字母。没有充分的理由使用MD5来做任何事情。在任何合理的情况下,它的质量都不能超过sha256。
- 我不想继续这个讨论,你只是在想你拒绝MD5。
- @Jasons——我认为,你拒绝拒绝拒绝一个完全可行的替换算法是有思想的,没有任何理由不使用它。"我学会了输入md5 darn,没有人会告诉我我不能。其他的字母,它们很奇怪,我的手指不能输入它们!"
- 我只需要纠正相同的图像,因此,使用hashlib.md5(open(full_path, 'rb').read()).hexdigest()就足够了。谢谢!
- @LittleZero-MD5比Sha256更容易输入吗?我只是在试探这个问题,因为不管在特定的环境中使用它有多安全,最好还是忘记已经存在的坏掉的算法。重新训练自己,不要想使用坏掉的算法,这样你就不会在重要的时候使用它。
- 我们应该释放资源。用WITH语句打开文件或编写代码关闭文件。
- @Rohitaneja-资源正在释放。文件对象立即与file_as_blockiter中的with语句关联。
- @我说的是前三个代码片段。除import hashlib [(fname, hashlib.md5(open(fname, 'rb').read()).digest()) for fname in fnamelst]外
- @Rohitaneja-啊,我的意思是坏例子。:-)是的,我想我应该解决这个问题。他们不应该是那种坏榜样。
很明显,我没有添加任何基本的新内容,但是在我达到评论状态之前添加了这个答案,加上代码区域使事情更加清晰——不管怎样,特别是从无所不能的答案回答@nemo的问题:
我曾经考虑过校验和(特别是在这里寻找关于块大小的建议),并且发现这个方法可能比您预期的要快。取最快的(但相当典型的)timeit.timeit或/usr/bin/time结果,这是对大约11MB的文件进行校验和的几种方法中的每一种的结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| $ ./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功能)与全神贯注的:
1 2 3 4 5 6 7
| 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次运行的速度总是快一点点),而mine通常在缓冲区耗尽后得到一个额外的f.read(blocksize),但是它是合理的可重复的,并且表明命令行上的md5sum并不一定比python实现更快……
edit:很抱歉耽搁了这么久,有一段时间没看这个了,但是要回答@edrandall的问题,我会写下一个adler32实现。但是,我还没有为它运行基准。基本上与CRC32相同:不是init、update和digest调用,而是zlib.adler32()调用:
1 2 3 4 5 6 7
| 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开始。需要使用ANDing使其成为32位无符号整数,从而确保它在Python版本中返回相同的值。
- 你能不能加几行比较sha1和zlib.adler32?
- 上面的md5sum()函数假定您对该文件具有写访问权。如果将open()调用中的"r+b"替换为"rb",它将正常工作。
- @Kevinlyda固定
- @埃德兰德尔:阿德勒32真的不值得打扰,例如leviathansecurity.com/blog/analysis-of-adler32
1
| hashlib.md5(pathlib.Path('path/to/file').read_bytes()).hexdigest() |
- 你好!请在代码中添加一些解释,说明为什么这是解决问题的方法。此外,这篇文章已经很老了,因此您还应该添加一些信息,说明为什么您的解决方案添加了其他人尚未解决的问题。