用 Python hashlib 校验下载文件:分块读取与 SHA-256 的边界
下载压缩包或安装文件后,可以先核对发布方提供的 SHA-256 摘要,再决定是否使用。本文写一个只读命令行工具:按块读取文件,输出实际摘要,并通过退出码区分一致、不一致和输入错误。摘要必须来自你信任的发布渠道;把自己刚算出的值再拿来对比自己,并不能验证下载是否正确。
先明确比较对象
程序比较的是文件字节,而不是文件名、扩展名或解压后的内容。同一内容改名后摘要不变;文本多一个换行,或压缩包重新打包,摘要都可能改变。因此应核对同一版本、同一平台和同一格式的文件,不能拿 Windows 安装包的摘要去验证 Linux 安装包。
可运行代码
将下面内容保存为 check_sha256.py,只需 Python 标准库。以二进制模式 rb 打开文件,避免文本解码和换行转换改变参与计算的数据。每次最多读取 1 MiB,并把数据依次交给同一个摘要对象;这里选择的是读取缓冲区大小,并非 SHA-256 算法的内部块大小。
import argparse
import hashlib
import re
from pathlib import Path
def sha256_file(path, chunk_size=1024 * 1024):
if chunk_size <= 0:
raise ValueError("chunk_size 必须为正数")
digest = hashlib.sha256()
with Path(path).open("rb") as stream:
for chunk in iter(lambda: stream.read(chunk_size), b""):
digest.update(chunk)
return digest.hexdigest()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="核对文件的 SHA-256 摘要")
parser.add_argument("file", type=Path)
parser.add_argument("expected", help="可信来源提供的 64 位十六进制摘要")
args = parser.parse_args()
if not re.fullmatch(r"[0-9a-fA-F]{64}", args.expected):
parser.error("expected 必须是 64 位十六进制字符串")
try:
actual = sha256_file(args.file)
except OSError as error:
parser.exit(2, f"读取失败:{error}\n")
print(actual)
matched = actual == args.expected.lower()
print("校验一致" if matched else "校验不一致")
raise SystemExit(0 if matched else 1)
先用一个可重复的小样本
下面的命令创建恰好包含三个字节 abc 的文件,不带换行。再用给定摘要运行检查,应该打印“校验一致”,退出码为 0。路径含空格时,运行命令应给路径加引号。
python3 -c "from pathlib import Path; Path('sample.bin').write_bytes(b'abc')"
python3 check_sha256.py sample.bin ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
把文件内容改为 abd,保留原来的期望摘要再运行,应提示“校验不一致”并返回 1。摘要字符串可以使用大写十六进制,代码会统一转成小写。长度不足、包含非十六进制字符、文件不存在或路径实际是目录时,程序返回 2;这些情况应当修正输入,不能当作校验通过。
分块读取解决什么问题
直接 read_bytes() 会一次读入整个文件。本例每读一块便更新摘要,避免为文件正文分配同等大小的缓冲区;文件越大,分块的价值越明显。不过程序仍然要读取全部内容,无法靠较小缓冲区跳过计算。空文件也有合法摘要,不应因为读取的第一块为空就把它当成异常。
核对结果的使用边界
摘要一致说明本次读到的数据与期望摘要相符,不等于软件已经经过安全审计,也不证明发布者身份。若文件和摘要都来自同一个被替换的页面,对比可能无法发现问题;需要结合可信发布渠道,必要时再验证数字签名。读取期间也应避免其他程序继续修改文件,本工具没有加锁,不能提供文件系统事务快照。
本文示例已在临时目录验证空文件、abc 样本、多块二进制内容、大写摘要、内容变化,以及缺失路径、目录和非法摘要的退出码;还验证了函数拒绝零缓冲区。实际校验时先确认版本与来源,再处理不一致的原因,避免仅因文件名相同就跳过检查。
参考:Python 官方 hashlib 文档:https://docs.python.org/zh-cn/3/library/hashlib.html 。本文说明、工具和验证样例为本次独立编写。
- 点赞
- 收藏
- 关注作者
评论(0)