在 Python 中散列文件

Posted

技术标签:

【中文标题】在 Python 中散列文件【英文标题】:Hashing a file in Python 【发布时间】:2014-03-30 06:20:53 【问题描述】:

我希望 python 读取到 EOF,以便我可以获得适当的哈希值,无论是 sha1 还是 md5。请帮忙。这是我目前所拥有的:

import hashlib

inputFile = raw_input("Enter the name of the file:")
openedFile = open(inputFile)
readFile = openedFile.read()

md5Hash = hashlib.md5(readFile)
md5Hashed = md5Hash.hexdigest()

sha1Hash = hashlib.sha1(readFile)
sha1Hashed = sha1Hash.hexdigest()

print "File Name: %s" % inputFile
print "MD5: %r" % md5Hashed
print "SHA1: %r" % sha1Hashed

【问题讨论】:

还有什么问题? 我希望它能够散列文件。无论文件大小如何,我都需要它读取到 EOF。 这正是file.read() 所做的——读取整个文件。 read() 方法的文档说? 你应该看看“什么是散列?”。 【参考方案1】:

TL;DR 使用缓冲区来不使用大量内存。

我相信,当我们考虑处理非常大的文件对内存的影响时,我们就找到了问题的症结所在。我们不希望这个坏男孩通过 2 GB 的内存来处理 2 GB 的文件,因此,正如 pasztorpisti 指出的那样,我们必须分块处理这些更大的文件!

import sys
import hashlib

# BUF_SIZE is totally arbitrary, change for your app!
BUF_SIZE = 65536  # lets read stuff in 64kb chunks!

md5 = hashlib.md5()
sha1 = hashlib.sha1()

with open(sys.argv[1], 'rb') as f:
    while True:
        data = f.read(BUF_SIZE)
        if not data:
            break
        md5.update(data)
        sha1.update(data)

print("MD5: 0".format(md5.hexdigest()))
print("SHA1: 0".format(sha1.hexdigest()))

我们所做的是,在我们使用 hashlib 的方便花花公子 update method 时,我们以 64kb 的块更新了这个坏男孩的哈希值。这样一来,我们使用的内存比一次对这个人进行哈希处理所需的 2gb 少得多!

您可以使用以下方法进行测试:

$ mkfile 2g bigfile
$ python hashes.py bigfile
MD5: a981130cf2b7e09f4686dc273cf7187e
SHA1: 91d50642dd930e9542c39d36f0516d45f4e1af0d
$ md5 bigfile
MD5 (bigfile) = a981130cf2b7e09f4686dc273cf7187e
$ shasum bigfile
91d50642dd930e9542c39d36f0516d45f4e1af0d  bigfile

希望有帮助!

所有这些都在右侧的链接问题中进行了概述:Get MD5 hash of big files in Python


附录!

一般来说,在编写 python 时,养成遵循pep-8 的习惯会有所帮助。例如,在 python 中,变量通常用下划线分隔而不是驼峰式。但这只是一种风格,没有人真正关心这些事情,除了那些不得不阅读糟糕风格的人......这可能是你多年后阅读这段代码。

【讨论】:

@ranman 您好,我无法获取 0".format(sha1.hexdigest()) 部分。为什么我们使用它而不是仅使用 sha1.hexdigest() ?跨度> @Belial 什么不起作用?我主要只是用它来区分两个哈希...... @ranman 一切正常,我只是从未使用过它,也没有在文献中看到它。 "0".format() ...我不知道。 :) 我应该如何选择BUF_SIZE 这不会产生与shasum 二进制文件相同的结果。下面列出的另一个答案(使用 memoryview 的那个)与其他散列工具兼容。【参考方案2】:

为了正确高效地计算文件的哈希值(在 Python 3 中):

以二进制模式打开文件(即将'b' 添加到文件模式)以避免字符编码和行尾转换问题。 不要将完整的文件读入内存,因为那样会浪费内存。相反,按块顺序读取它并更新每个块的哈希值。 消除双缓冲,即不使用缓冲 IO,因为我们已经使用了最佳块大小。 使用readinto() 避免缓冲区搅动。

例子:

import hashlib

def sha256sum(filename):
    h  = hashlib.sha256()
    b  = bytearray(128*1024)
    mv = memoryview(b)
    with open(filename, 'rb', buffering=0) as f:
        for n in iter(lambda : f.readinto(mv), 0):
            h.update(mv[:n])
    return h.hexdigest()

【讨论】:

你怎么知道什么是最佳块大小? @Mitar,下限是物理块的最大值(传统上为 512 字节或 4KiB,使用较新的磁盘)和系统页面大小(许多系统上为 4KiB,其他常见选择:8KiB 和 64 KiB )。然后你基本上做一些基准测试和/或查看已发布的benchmark results and related work(例如检查当前 rsync/GNU cp/... 使用什么)。 @jpmc26,getpagesize() 在这里不是很有用 - 常见的值是 4 KiB 或 8 KiB,在那个范围内,即比 128 KiB 小得多 - 128 KiB 通常是一个好的选择。 mmap 在我们的用例中没有多大帮助,因为我们从前到后顺序读取完整的文件。当访问模式更随机访问时,mmap 具有优势,例如,如果页面被多次访问和/或如果它简化了读取缓冲区管理。 我用大约 116GB 的文件和 sha1sum 算法对 (1) @Randall Hunt 和 (2) 你的解决方案(按此顺序,由于文件缓存很重要)进行了基准测试。修改解决方案 1 以使用 20 * 4096 (PAGE_SIZE) 的缓冲区并将缓冲参数设置为 0。修改解决方案 2 仅算法 (sha256 -> sha1)。结果:(1) 3m37.137s (2) 3m30.003s 。二进制模式下的原生sha1sum:3m31.395s @Murmel '等大小的文件'是什么意思?这个答案是一个通用的解决方案。如果你用buffering=0 调用open(),它不会做任何缓冲。 Mitar 的答案实现了缓冲区搅动。【参考方案3】:

我编写了一个模块,它能够使用不同的算法对大文件进行哈希处理。

pip3 install py_essentials

像这样使用模块:

from py_essentials import hashing as hs
hash = hs.fileChecksum("path/to/the/file.txt", "sha256")

【讨论】:

是否跨平台(Linux + Win)?它适用于 Python3 吗?还维护吗? 是的,它是跨平台的,仍然可以工作。包中的其他东西也可以正常工作。但我将不再维护这个个人实验包,因为这只是我作为开发人员的一次学习。【参考方案4】:
import hashlib
user = input("Enter ")
h = hashlib.md5(user.encode())
h2 = h.hexdigest()
with open("encrypted.txt","w") as e:
    print(h2,file=e)


with open("encrypted.txt","r") as e:
    p = e.readline().strip()
    print(p)

【讨论】:

你基本上是在做echo $USER_INPUT | md5sum > encrypted.txt && cat encrypted.txt,它不处理文件的散列,尤其是大文件。 散列 != 加密【参考方案5】:

我会简单地提议:

def get_digest(file_path):
    h = hashlib.sha256()

    with open(file_path, 'rb') as file:
        while True:
            # Reading is buffered, so we can read smaller chunks.
            chunk = file.read(h.block_size)
            if not chunk:
                break
            h.update(chunk)

    return h.hexdigest()

这里的所有其他答案似乎都太复杂了。 Python 在读取时已经在缓冲(以理想的方式,或者如果您有更多关于底层存储的信息,则可以配置该缓冲),因此最好以块的形式读取散列函数认为理想的块,这样可以更快或至少减少 CPU 密集度计算哈希函数。因此,您无需禁用缓冲并尝试自己模拟,而是使用 Python 缓冲并控制您应该控制的内容:数据的使用者找到理想的散列块大小。

【讨论】:

完美的答案,但如果你能用相关文档支持你的陈述,那就太好了:Python3 - open() 和 Python2 - open()。即使介意两者之间的差异,Python3 的方法更加复杂。不过,我真的很欣赏以消费者为中心的观点! hash.block_size 被记录为“哈希算法的内部块大小”。 Hashlib 觉得它理想。包文档中没有任何内容表明 update() 更喜欢 hash.block_size 大小的输入。如果你这样称呼它,它不会使用更少的 CPU。您的 file.read() 调用会导致许多不必要的对象创建和从文件缓冲区到新块字节对象的多余副本。 哈希在block_size 块中更新它们的状态。如果您没有在这些块中提供它们,它们必须缓冲并等待足够的数据出现,或者在内部将给定的数据拆分为块。因此,您可以在外部处理它,然后简化内部发生的事情。我觉得这是理想的。例如:***.com/a/51335622/252025 block_size 远小于任何有用的读取大小。此外,任何有用的块和读取大小都是 2 的幂。因此,读取大小可被所有读取的块大小整除,可能是最后一个读取除外。例如,sha256 块大小为 64 字节。这意味着update() 能够直接处理输入,而无需任何高达block_size 的任何倍数的缓冲。因此,只有当最后一次读取不能被块大小整除时,它才必须缓冲一次最多 63 个字节。因此,您最后的评论不正确,不支持您在回答中提出的主张。 这个解决方案不符合简单的基准!在我的 1Gb 文件中,它的速度 (5.38s) 是 Randall Hunt 的答案 (2.18s) 的两倍多,后者本身比 maxschlepzig 的答案 (2.13s) 慢得多。【参考方案6】:

这是一个 Python 3、POSIX 解决方案(不是 Windows!),它使用 mmap 将对象映射到内存中。

import hashlib
import mmap

def sha256sum(filename):
    h  = hashlib.sha256()
    with open(filename, 'rb') as f:
        with mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ) as mm:
            h.update(mm)
    return h.hexdigest()

【讨论】:

天真的问题......在这种情况下使用mmap 有什么好处? @JonathanB。大多数方法不必要地在内存中创建bytes 对象,并调用read 太多或太少的次数。这会将文件直接映射到虚拟内存中,并从那里对其进行哈希处理——操作系统可以将文件内容直接从缓冲区缓存映射到读取过程中。这意味着这可能会比this one 更快 我将此与逐块读取块的方法进行了基准测试。这种方法需要 3GB 内存来散列 3GB 文件,而 maxschlepzig 的答案需要 12MB。他们在我的 Ubuntu 机器上花费的时间大致相同。 FWIW,Python >= 3.8 可以添加mm.madvise(mmap.MADV_SEQUENTIAL) 以在一定程度上减少缓冲区缓存压力。 FWIW,使用“access=mmap.ACCESS_READ”而不是“prot=mmap.PROT_READ”可以在 Windows 上工作(但它比简单地读取块稍慢)【参考方案7】:

FWIW,我更喜欢这个版本,它与 maxschlepzig 的答案具有相同的内存和性能特征,但 IMO 更具可读性:

import hashlib

def sha256sum(filename, bufsize=128 * 1024):
    h = hashlib.sha256()
    buffer = bytearray(bufsize)
    # using a memoryview so that we can slice the buffer without copying it
    buffer_view = memoryview(buffer)
    with open(filename, 'rb', buffering=0) as f:
        while True:
            n = f.readinto(buffer_view)
            if not n:
                break
            h.update(buffer_view[:n])
    return h.hexdigest()

【讨论】:

以上是关于在 Python 中散列文件的主要内容,如果未能解决你的问题,请参考以下文章

在Python中散列一个整数以匹配Oracle的STANDARD_HASH

在 Python 中散列文件

在 .NET 中散列 SecureString

在Python中散列数组

MongoDB游标在Perl中散列

无法在平均堆栈中散列密码