Python SHAKE 可变长度摘要:连续取两次四字节,为什么拼不成一次八字节

前天 15阅读

SHAKE允许调用方指定摘要长度,很容易被误用成一个可以连续read的字节流。循环调用digest(4),想分三次取出十二个不同字节,实际却反复得到开头四字节。长度可变,不代表对象内部存在会随着digest调用前进的读取位置。

下面在Linux、CPython 3.12.14实跑,只计算固定字节串的摘要。保存为demo.py,运行python3 demo.py。四字节长度仅用于看清输出差异,不是业务安全参数建议。实验用断言检查前缀、拼接、输入更新和快照,最后给出明确的分块方式。

Python SHAKE 可变长度摘要:连续取两次四字节,为什么拼不成一次八字节

AI模型生成概念插图:长短两条色带从同一个起点展开,短带重合于长带前段,表示不同长度请求共享摘要前缀;不是密码系统界面。

完整程序与本地结果

完整可运行程序

import hashlib

h = hashlib.shake_128(b"abc")
first = h.digest(4)
second = h.digest(4)
longer = h.digest(8)
assert first == second == longer[:4]
assert first.hex() == "5881092d"
assert longer.hex() == "5881092dd818bf5c"
assert first + second != longer
print("first request:", first.hex())
print("second request:", second.hex())
print("longer request:", longer.hex())
print("two short requests equal one long:", first + second == longer)
hex_text = h.hexdigest(4)
assert len(first) == 4 and len(hex_text) == 8
print("digest bytes / hex characters:", len(first), len(hex_text))

snapshot = h.copy()
h.update(b"d")
changed = h.digest(8)
assert changed == hashlib.shake_128(b"abcd").digest(8)
assert changed != longer
assert snapshot.digest(8) == longer
print("updated input:", changed.hex())
print("snapshot retains old prefix:", snapshot.digest(8) == longer)

whole = snapshot.digest(12)
chunks = [whole[i:i + 4] for i in range(0, len(whole), 4)]
assert b"".join(chunks) == whole
print("explicit chunks:", [chunk.hex() for chunk in chunks])

本次实际输出(以下为结果,不是程序)

first request: 5881092d
second request: 5881092d
longer request: 5881092dd818bf5c
two short requests equal one long: False
digest bytes / hex characters: 4 8
updated input: 5360ce03ad4df5bb
snapshot retains old prefix: True
explicit chunks: ['5881092d', 'd818bf5c', 'f8a3ddb7']

重复调用取得相同前缀

前两次digest(4)都得到5881092d,digest(8)得到5881092dd818bf5c。短结果等于长结果的前四字节,但两份短结果拼接后重复了开头,与真正八字节结果不同。失败的拼接比较正是把它误当连续流时会出现的问题。

digest的输入是目前已交给update的全部数据,参数只决定本次需要多少摘要字节。取摘要本身不会消费其中一段,也不会把下一次起点挪到后面。若接口需要固定总长度,先明确总量,再对同一结果切块,位置管理就能直接被检查。

十六进制参数仍然按字节计数

hexdigest(4)返回八个十六进制字符,因为每个字节用两个字符表示。输出digest bytes / hex characters为4和8。这不是摘要突然变长了一倍,而是原始字节与展示文本使用了不同计量单位。保存字段或协议长度时要写清采用哪一种。

程序先复制h,再向原对象update一个字节d。新的结果等于对abcd重新计算,说明update追加的是输入数据,并不是让输出游标向后移动。snapshot仍返回abc的原前缀,适合保留某个输入阶段的摘要状态;它也不是另一段输出流。

要分块,就让偏移显式存在

最后一次取得十二字节,再按四字节切成三块,得到5881092d、d818bf5c与f8a3ddb7,重新连接恰好恢复完整结果。这个实现适合已知且适度的总长度,容易测试,也避免循环调用digest时无意重复前缀。

若需要极长输出,不应不加评估地一次分配巨大字节串;本接口没有read式游标,要按所选库实际提供的能力设计。固定输入的摘要是确定性的,不能仅因看起来随机就拿示例代替随机令牌生成。排查不一致时记录算法、完整输入字节、请求长度和展示编码,分别定位改变发生在哪一步。

参考资料

资料核验日期:2026年10月2日。以上输出对应文中固定输入和明确运行版本,本地执行退出码为0。

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。