跳到正文
效率工具实用笔记关于本站
文件整理

两份文件内容是否相同:分块计算 SHA-256 摘要的入门做法

约 8 分钟阅读AI 辅助整理

用自建两个文件演示分块读取并 update 到同一个 sha256 对象,再用 hexdigest 比较十六进制摘要;摘要相同只是内容一致的参考线索,不是绝对证明,也不能用于密码存储。

本文要点

  1. 开始前请把练习文件放在单独目录,例如 exercise/,不要拿重要文件直接试

  2. 分块读取的写法是:以二进制模式打开文件,循环读取固定大小的块,每读到一块就 update 一次

两份文件内容是否相同:分块计算 SHA-256 摘要的入门做法的知识要点示意图
依据已审核正文摘取要点制作的示意图,非实物照片;请结合正文中的条件与范围阅读。要点摘自已审核正文;完整条件与说明见正文。查看高清图解 ↗
本文目录

先回答:分块算摘要,比的是两个十六进制字符串

如果你手上有两个文件,想知道内容是不是一样,可以分别对它们计算 SHA-256 摘要,再比较两个 hexdigest 的结果。

按 Python 官方 hashlib 文档的说明,sha256() 会创建一个 SHA-256 哈希对象,你可以用 update 方法不断喂给它 bytes 类型的数据,最后用 hexdigest() 取得到目前为止所有已喂入数据拼接结果的摘要,返回值是只含十六进制数字的字符串。

关键点在这里:文档明确写了,重复调用 update 等价于把参数拼接后一次调用,也就是 m.update(a); m.update(b) 和 m.update(a+b) 结果相同。所以你可以一块一块地读文件、一块一块地 update,不必把整个文件一次读进内存。

本文只处理一件事:只读自建的两个练习文件,分块 update,比较 hexdigest。不涉及写入、删除、重命名,也不涉及其他哈希算法。

一个必须提前说清的边界:摘要相同只能说明在你这次计算里两个文件喂进去的字节序列一致,它不是绝对数学证明,也不能用来存密码。文档在讲密钥派生时专门提醒,像 sha1(password) 这种朴素做法不能抵抗暴力破解,好的密码哈希函数应当可调、慢、并带盐。SHA-256 摘要不适合当密码存储方案。

自建两个练习文件:先造出“相同”和“不同”两种对照

开始前请把练习文件放在单独目录,例如 exercise/,不要拿重要文件直接试。下面所有路径和内容都是自建练习示例,不是对真实文件的记录。

先造两个内容相同的文件,再造一个内容不同的,方便你对照结果:

from pathlib import Path

base = Path("exercise/hash_demo")
base.mkdir(parents=True, exist_ok=True)

(base / "a.txt").write_bytes(b"same content\n")
(base / "b.txt").write_bytes(b"same content\n")
(base / "c.txt").write_bytes(b"same content\nchanged\n")

这里用 write_bytes 写入明确的字节,避免编码差异干扰练习。a.txt 和 b.txt 内容一致,c.txt 多了一段。

为什么强调“字节”:update 接受的是 bytes-like 对象,摘要算的是字节序列,不是字符。同一个字符在不同编码下字节不同,摘要也会不同。所以比较文件时,你比较的其实是文件的原始字节。

分块读取并 update:一次读多少、循环怎么写

分块读取的写法是:以二进制模式打开文件,循环读取固定大小的块,每读到一块就 update 一次。

import hashlib
from pathlib import Path

def file_sha256(path, chunk_size=65536):
    h = hashlib.sha256()
    with open(path, "rb") as f:
        while True:
            block = f.read(chunk_size)
            if not block:
                break
            h.update(block)
    return h.hexdigest()

几个要点:

1. 打开模式必须是 "rb",二进制读取。文本模式会做换行转换和编码解码,喂给 update 的就不是文件原始字节了。

2. chunk_size 是每次读多少字节,65536 只是本示例选的一个值。文档没有规定你必须用某个块大小,它只保证分多次 update 与一次 update 拼接结果等价。

3. 循环用 read 返回空字节串作为结束条件,读到末尾就退出。

4. 每个文件都要新建一个 sha256() 对象。不要复用同一个对象去算第二个文件,否则第二个文件的摘要会带着第一个文件的数据。

比较结果:三个文件分别得到什么

用上面的函数分别算三个文件:

for name in ["a.txt", "b.txt", "c.txt"]:
    print(name, file_sha256(base / name))

预期你会看到:a.txt 和 b.txt 打印出相同的十六进制字符串,c.txt 打印出不同的字符串。

比较时注意两点:

1. hexdigest() 返回的是字符串,直接用 == 比较即可。不要用 digest() 的 bytes 去和字符串比,两者类型不同。

2. 摘要字符串应当长度一致。SHA-256 的摘要固定为 32 字节,hexdigest 是双倍长度的十六进制字符串。如果你看到两个结果长度不一样,说明比较的不是同一种算法或同一种输出形式,先检查代码。

一个提醒:a.txt 和 b.txt 摘要相同,是因为它们字节完全一致。这不能反过来证明“摘要相同就一定内容相同”——哈希函数理论上存在碰撞可能,文档也专门警告 MD5、SHA1 等算法有已知的碰撞弱点。SHA-256 目前没有在本文资料里被列为已知碰撞弱点,但“摘要相同”仍然只是内容一致的一个参考线索,不是绝对数学证明。

出错时先查这几处:读不到、结果不对、对象复用

如果结果和预期不符,按下面顺序排查:

1. 文件打不开。检查路径是不是写错,或者文件是不是真的在 exercise/hash_demo 下。用 Path.exists() 先确认路径存在。

2. 两个“相同”的文件摘要却不同。最常见原因是打开模式不是 "rb",或者其中一个文件在保存时被编辑器改了换行、加了末尾空行。用 write_bytes 写入明确字节可以排除这类干扰。

3. 摘要算出来不对。检查是不是复用了同一个哈希对象。每个文件必须新建 sha256()。

4. 算法不可用。文档说明,某些受限环境(例如少见的 FIPS 合规构建,或 OpenSSL 配置了 FIPS 模式)可能缺少或屏蔽某些算法,调用不可用算法的构造函数会抛出 ValueError。如果你遇到这种情况,先确认当前环境是否支持 sha256。

5. 读的是文本不是字节。如果你不小心用 "r" 打开,update 会拿到字符串而不是 bytes,会报类型错误。改回 "rb"。

适用范围与使用限制

本文只覆盖:只读自建的两个(或三个)练习文件,按块 update 到 sha256 对象,用 hexdigest 比较十六进制摘要。

以下内容不在本文范围内,资料没有支持,本文也不编造:

- 不涉及其他哈希算法(如 MD5、SHA1、SHA3、BLAKE2)的具体用法和性能比较。

- 不涉及文件写入、删除、重命名等会改动文件的操作。

- 不涉及大文件性能优化、并行计算或多线程细节。

- 不给任何“摘要相同即安全”“摘要相同即文件未被篡改”的结论。

两个必须记住的边界:

第一,摘要相同不是绝对数学证明。它只能说明这次计算中喂入的字节序列一致。哈希函数存在碰撞的理论可能,文档也警告部分算法有已知碰撞弱点。

第二,SHA-256 摘要不用于密码存储。文档在密钥派生部分明确说,朴素哈希不能抵抗暴力破解,密码哈希应当可调、慢、带盐,并给出了 pbkdf2_hmac、scrypt 这类专门函数。本文的摘要比较只适合文件内容核对这类场景。

参考资料:Python 官方 hashlib 文档,https://docs.python.org/3/library/hashlib.html。

观看知识要点视频 18 秒

18秒无旁白要点导览,文字来自已审核正文;完整步骤、条件及参考资料见下方。

参考资料

以下原始资料提供本文依据;请结合日期、适用条件和产品说明查阅。

请结合原始资料的适用条件阅读。

返回文件整理 →