关于python:生成文件的MD5校验和

Generating an MD5 checksum of a file

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


您可以使用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(),因此不必转换回。


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

单个文件:

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]


很明显,我没有添加任何基本的新内容,但是在我达到评论状态之前添加了这个答案,加上代码区域使事情更加清晰——不管怎样,特别是从无所不能的答案回答@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版本中返回相同的值。


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