生成fi的MD5校验和

2024-04-18 10:22:21 发布

您现在位置:Python中文网/ 问答频道 /正文


Tags: python
3条回答

很明显,我没有添加任何基本的新内容,但是在我开始评论状态之前添加了这个答案,加上代码区域使事情更清楚——无论如何,特别是从Omnifarious的答案中回答@Nemo的问题:

我碰巧在考虑校验和(特别是来这里寻找关于块大小的建议),并且发现这个方法可能比您预期的要快。对大约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对于一个11MB文件都需要大约30ms的时间。相关的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总是要快一点点),而mine通常在缓冲区用完后会得到一个额外的f.read(blocksize),但它是合理可重复的,并且表明命令行上的md5sum并不一定比Python实现快。。。

编辑:很抱歉耽搁了很长时间,有一段时间没看这个了,但是为了回答@EdRandall的问题,我将写下一个Adler32实现。然而,我还没有运行它的基准。它基本上与CRC32相同:不是init、update和digest调用,而是一个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-ing使其成为32位无符号整数,从而确保在Python版本中返回相同的值。

你可以使用hashlib.md5()

请注意,有时无法将整个文件放入内存。在这种情况下,必须按顺序读取4096字节的数据块,并将它们馈送给Md5函数:

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(),那么您不必转换回。

有一种方法相当于内存不足。

单个文件:

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 is known broken不应该用于任何目的,因为漏洞分析可能非常棘手,而且分析代码将来可能用于解决安全问题的任何可能用途都是不可能的。IMHO,应该把它从库中完全删除,这样每个使用它的人都会被迫更新。所以,你应该这样做:

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

如果你只需要128位的摘要,你可以做.digest()[:16]

这将为您提供一个元组列表,每个元组包含其文件名及其散列。

我再次强烈质疑你使用MD5。您至少应该使用SHA1,并给定recent flaws discovered in 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]

相关问题 更多 >