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

hash.update分多次调用与一次调用等价吗:拼接顺序与hexdigest结果

约 7 分钟阅读AI 辅助整理

等价。按Python官方hashlib文档,m.update(a)后再m.update(b),与m.update(a+b)结果相同;digest返回bytes,hexdigest返回十六进制字符串;copy()可克隆哈希对象,用于共享前缀数据分别计算摘要。

本文要点

  1. 下面用一段自建的练习数据,把两种写法并排跑一遍,观察hexdigest是否一致

  2. 拿到哈希对象之后,取结果有两个方法,它们回答的是同一个摘要的两种表示形式

  3. 如果你有两段数据,开头一大截完全相同,只有结尾不同,不必把公共部分重复喂两遍

hash.update分多次调用与一次调用等价吗:拼接顺序与hexdigest结果的知识要点示意图
依据已审核正文摘取要点制作的示意图,非实物照片;请结合正文中的条件与范围阅读。要点摘自已审核正文;完整条件与说明见正文。查看高清图解 ↗
本文目录

先回答:多次update等价于把参数按顺序拼接后一次update

如果你把同一段数据拆成几块,分几次调用update,得到的摘要和一次性传入拼接后的完整数据是一样的。

按Python官方hashlib文档的说明,hash.update(data)用字节类对象更新哈希对象,重复调用等价于把全部参数拼接后一次调用:m.update(a); m.update(b) 等价于 m.update(a+b)。

这里有两个前提要抓住。第一,传入的必须是字节类对象,通常是bytes,不是普通字符串。第二,等价的前提是拼接顺序一致。先喂a再喂b,等价于喂a+b;如果先喂b再喂a,等价于喂b+a,那是另一段字节序列,摘要通常不同。

所以判断顺序是:先确认每次update喂进去的字节内容,再确认喂入的先后顺序,最后看hexdigest结果。本文只讲这一条等价关系、digest与hexdigest的区别,以及copy()克隆哈希对象的用途。下面所有示例都是自建的练习数据,只操作练习目录,不碰你正在用的文件。动手前建议在练习目录里新建脚本,例如 exercise/hash_demo.py。

自建示例:分两次update和一次update对比hexdigest

下面用一段自建的练习数据,把两种写法并排跑一遍,观察hexdigest是否一致。

import hashlib

part1 = b"Nobody inspects"
part2 = b" the spammish repetition"

m1 = hashlib.sha256()
m1.update(part1)
m1.update(part2)
print(m1.hexdigest())

m2 = hashlib.sha256()
m2.update(part1 + part2)
print(m2.hexdigest())

两行打印出来的十六进制字符串应当相同。原因就是文档写的那条等价关系:分次调用等价于拼接后一次调用。

再换一个顺序做对照,把part2先喂进去:

m3 = hashlib.sha256()
m3.update(part2)
m3.update(part1)
print(m3.hexdigest())

这一次的结果通常和前两行不同,因为它对应的是part2 + part1这段字节序列,而不是part1 + part2。这说明“等价”不是随便拆都行,拆块可以,顺序不能变。

一个提醒:上面所有字节内容都是练习用的自建数据,不是对某个真实文件的记录。

digest返回bytes,hexdigest返回双倍长度的十六进制字符串

拿到哈希对象之后,取结果有两个方法,它们回答的是同一个摘要的两种表示形式。

按文档说明,digest()返回到目前为止传给update()的数据的摘要,类型是bytes对象,长度等于digest_size,取值范围是0到255的字节。hexdigest()与digest()类似,区别在于返回的是字符串对象,长度是digest()的两倍,只包含十六进制数字。

所以两者的关系可以这样理解:同一份摘要,digest()给你原始字节,hexdigest()给你便于在邮件或其他非二进制环境里交换的十六进制文本。一个字节对应两个十六进制字符,长度正好翻倍。

选择依据很简单:后面要按字节处理、要写进二进制文件,用digest();要打印、要贴进文本、要和人比对,用hexdigest()。本文只讲这两种表示形式的区别,不涉及其他编码方式。

需要说清的是,hexdigest()返回的字符串只含十六进制数字,它本身不是加密结果,也不能用来还原原始数据。

copy()克隆哈希对象:共享前缀数据分别算摘要

如果你有两段数据,开头一大截完全相同,只有结尾不同,不必把公共部分重复喂两遍。

按文档说明,hash.copy()返回哈希对象的一个副本,也就是克隆。文档给出的用途是:可以高效地计算共享同一初始子串的数据的摘要。

一个自建示例,先喂公共前缀,再克隆出两个对象,各自接上不同的结尾:

import hashlib

prefix = b"shared-prefix-"
base = hashlib.sha256()
base.update(prefix)

h_a = base.copy()
h_a.update(b"ending-A")

h_b = base.copy()
h_b.update(b"ending-B")

print(h_a.hexdigest())
print(h_b.hexdigest())

这样写的好处是,prefix只喂了一次,两个副本各自从同一个中间状态出发,分别接上不同结尾。两个hexdigest通常不同,因为它们对应的完整字节序列不同。

要注意的是,克隆出来的是独立对象。对副本继续update,不会影响原对象;反过来,原对象之后继续update,也不会改变已经克隆出去的那份状态。本文只讲copy()这个用途,不涉及多线程或性能调优。

检查结果时容易踩的三个点

第一,喂进去的必须是字节类对象。文档写的是用bytes-like object更新哈希对象。如果你手里是文本,要先按某个编码转成bytes,再update。转码方式不同,喂进去的字节就不同,摘要自然不同。

第二,拼接顺序决定结果。分次update可以,但先后顺序要和你想对应的完整字节序列一致。想算part1 + part2,就先喂part1再喂part2,不要反过来。

第三,比较结果时用同一种表示。两个摘要一个用digest()取、一个用hexdigest()取,类型不同,直接比会得到False。要比就都用hexdigest(),或者都用digest()。

一个自建核对方法:把两种写法的hexdigest各自存进一个变量,打印出来逐字符对照。相同就是等价关系成立,不同就先回头检查喂入顺序和字节内容,而不是先怀疑算法。

以上核对动作都是编辑整理的检查方向,不是对某个真实文件的测试记录。

适用限制与参考资料

本文只讲三件事:update多次调用等价于拼接后一次调用、digest与hexdigest的区别、copy()克隆哈希对象的用途。不涉及密码存储,不讲密钥派生,也不涉及任何茶叶内容。

需要留意的边界:文档提到,部分算法存在已知的哈希碰撞弱点,包括MD5和SHA1。本文不展开这些算法的安全性比较,也不据此给出选用建议。

另外,文档说明,当一次传入超过2047字节的数据时,计算哈希期间会释放Python的GIL,以支持多线程。本文不展开这一行为的细节和影响。

本文所有示例都是自建的练习数据,只操作练习目录里的脚本,不读写你正在用的文件,也不执行删除或系统修改。动手前建议在练习目录里新建脚本,例如 exercise/hash_demo.py,不要拿重要文件直接试。

事实依据来自Python官方hashlib模块文档,链接见source字段。

观看知识要点视频 18 秒

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

参考资料

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

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

返回文件整理 →