第二章 · 第三课 · Hash functions

改一个字,为什么整串指纹都变了?

哈希把任意长度的数据压成固定长度的“数字指纹”。它不能加密秘密,却是交易标识、区块关联、状态承诺、地址与合约调用的基础。

  • 从零开始
  • 约 55 分钟
  • 含互动实验
  • 无需数学前置
Input · 任意长度ethereum
Deterministic functionKeccak-256
Digest · 固定长度54111124…a255155
256 bit

256 位 = 32 字节 = 64 个十六进制字符。前缀 0x 只是显示惯例,不计入摘要本身。

00

哈希处理的不是“意思”,而是一串精确的比特

先忘掉区块链。把密码学哈希想成一台公开、无密钥、每个人都能重复运行的指纹机:输入任意字节,输出固定长度摘要。

文本、图片、程序、交易或整棵数据树,进入哈希函数前都必须先变成字节。函数看不懂“以太坊”是什么意思,也不知道两份 JSON 在语义上是否等价;它只认每一位是 0 还是 1。

人看到的文本ethereum
UTF-8 字节65 74 68 65
72 65 75 6d
Keccak-25654111124…
a255155

因此,同一个“意思”如果编码不同,摘要就可能完全不同:大小写、空格、换行、Unicode 规范化、字段顺序和序列化方式都属于输入的一部分。区块链协议必须先规定唯一的编码规则,哈希才有共同答案。

H : {0,1}* → {0,1}256
任意长度比特串 → 恰好 256 位摘要

四个马上要拆掉的误解

A

哈希不是压缩文件

普通压缩需要可逆;哈希故意丢弃海量信息,只保留固定长度承诺。

B

哈希不是加密

没有密钥,也没有“解密”操作。任何人都能计算同一输入的摘要。

C

摘要不是随机数

输出看起来杂乱,但同一算法与同一字节输入永远得到同一结果。

D

摘要不是现实证明

它能支持字节完整性校验,不能证明输入描述的链外事件真实。

01

好的密码学哈希,需要同时守住六道门

“输出 256 位”只是格式。真正让哈希可用于安全系统的,是确定性、快速计算、雪崩扩散,以及三类难以逆向或伪造的安全目标。NIST 将原像、第二原像与碰撞抗性列为密码学哈希的核心性质。[1]

01 · Deterministic

确定性

任何机器在同一算法上处理同一字节串,必须得到完全相同的摘要。

02 · Fixed length

固定长度

空字符串与一整部电影,经 SHA-256 或 Keccak-256 都输出 256 位。

03 · Efficient

正向计算高效

验证者需要迅速复算摘要;安全来自反向问题很难,而非正向计算很慢。

04 · Avalanche

雪崩效应

输入只变 1 位,输出每一位都应近似以 50% 概率翻转,变化迅速扩散。

05 · One-way

原像与第二原像抗性

给定摘要很难找出任意输入;给定一份输入也很难找出同摘要的另一份。

06 · Collision resistant

碰撞抗性

很难主动找到任意两份不同输入,使它们产生完全相同的摘要。

重要区分

雪崩效应让输出“看起来充分混合”,但仅凭雪崩现象,不能证明算法抗碰撞或抗逆向。

一个设计很差的函数也可能让许多位变化;密码学安全还需要公开分析、标准化与长期密码分析检验。
02

亲手改一个字符,看 256 位怎样“雪崩”

ethereumEthereum 的 UTF-8 编码只差一个比特:小写 e = 0x65,大写 E = 0x45,差值正是 0x20

互动图示 · 单比特雪崩实验

编辑任意输入;紫色十六进制位与方格表示两份摘要的差异。

Digest A 541111248b45b7a8dc3f5579f630e74cb01456ea6ac067d3f4d793245a255155
Digest B 564ccaf7594d66b1eaaea24fe01f0585bf52ee70852af4eac0cc4b04711cd0e2
126 / 256 49.2% 输出位翻转

等待本地算法自检;静态示例不依赖脚本运行。

静态示例:Keccak-256 下,两份仅差 1 个输入比特的数据有 126 / 256 个输出比特不同。当前环境尚未完成互动计算自检。

“理想上约一半”是统计期望,不是每次都必须恰好 128 位。上面的 Keccak-256 示例变化 126 位;切换 SHA-256,同一对输入变化 131 位。单次结果偏离 128 很正常。

雪崩效应带来两项直观价值:数据被改动时,摘要几乎不可能只“小改一点”;观察摘要也看不出输入之间是否相似。但请记住,相似度消失不等于内容被隐藏——如果输入候选很少,攻击者仍可逐个猜测并比对。

03

“单向”不是一个问题,而是三种不同的攻击目标

安全不是“绝对做不到”,而是在当前算法、算力与时间尺度下计算上不可行。对于行为理想的 256 位哈希,不同目标需要的工作量不同。[2]

01 · Preimage

原像攻击

给定 y,找 x:H(x) = y

只看到某个摘要,尝试找出任意能产生它的输入。理想 256 位哈希约需 2256 级工作。

02 · Second preimage

第二原像攻击

给定 x,找 x′ ≠ x:H(x′) = H(x)

已有一份合法文件,试图制造另一份同摘要文件。一般目标强度接近 256 位。

03 · Collision

碰撞攻击

找任意 x ≠ x′:H(x) = H(x′)

攻击者可同时挑两边,因生日悖论只需约 2128 级尝试,而不是 2256

04 · Pigeonhole

碰撞必然存在

无限输入 → 只有 2²⁵⁶ 个输出

抽屉原理决定不同输入必会共享摘要;“抗碰撞”只承诺难以找到,不承诺不存在。

为什么碰撞强度只剩一半?

如果只指定一个摘要,你像在 2256 个格子里找某一个格子;如果只要求任意两次落进同一格,样本之间可以两两配对。大约取到 2128 个随机样本时,配对数已经接近 2256,碰撞概率开始变得显著。这就是生日界。

2⁶⁴ 次
仍很低
2¹²⁸ 次
碰撞界
2¹⁹² 次
远超碰撞界
2²⁵⁶ 次
原像界
数量级,而非运行时预测

2128 依然巨大;但“256 位输出”不能被简单叫作“256 位碰撞安全”。

这里描述理想通用攻击的数量级。具体算法的真实强度还取决于密码分析、实现错误、输出截断与协议怎样使用摘要。
04

SHA-256:把消息切成 512 位分组,逐块压入状态

SHA-256 属于 SHA-2 家族,由 NIST 的 FIPS 180-4 规定。它不是把整份消息一次扔进黑箱,而是预处理后分块,以八个 32 位工作字为内部状态,重复压缩。[3]

SHA-256

Merkle–Damgård 式迭代结构 · 512 位消息分组 · 64 轮压缩 · 256 位输出

01 预处理 追加 1、若干 0 和原消息长度,使总长度成为 512 位的整数倍。
02 消息调度 每个 512 位分组先拆成 16 个 32 位字,再扩展为 W[0…63]。
03 64 轮压缩 八个工作字 a…h 经旋转、异或、选择、加法与轮常量不断混合。
04 链入状态 本分组结果加回八个状态字;最后拼成 8 × 32 = 256 位摘要。
Σ / σ不同距离的循环右移与右移,让每一位影响多个位置。
Ch / Maj按位选择与多数函数,以非线性方式混合工作字。
mod 2³²32 位模加法丢弃进位,配合异或和旋转形成扩散。

每个分组的输出会成为下一个分组的输入状态,因此消息前面的一点变化会一路传到最终摘要。正向运行很快,但从最终八个状态字反推所有分组,在设计目标下不可行。

进阶阅读:不要把裸 SHA-256 当作 MAC

这类迭代结构具有经典的长度扩展性质:拿到 SHA256(secret || message) 后,只要知道或猜中 secret || message 的总长度,攻击者就可能从旧摘要继续内部状态,为 message || glue_padding || attacker_suffix 构造有效摘要,而无需知道秘密本身;未知密钥长度通常还可以枚举。因此需要消息认证时应使用经过证明的 HMAC-SHA-256,不要自创“秘密前缀 + 哈希”协议。Keccak 海绵不受这一经典 Merkle–Damgård 长度扩展方式影响,但认证仍应使用标准 MAC 或数字签名方案。

05

Keccak-256:先吸收,再搅动,最后挤出

Keccak 采用海绵结构:内部维护 1600 位状态,把输入分块“吸收”进可见区域,每块之间执行 Keccak-f[1600] 置换;吸收完再从状态中“挤出”摘要。[4]

Keccak-256

Sponge 海绵结构 · rate 1088 位 · capacity 512 位 · 24 轮置换 · 256 位输出

ABSORB 吸收输入 填充后按 136 字节分块;每块与 rate 区域异或。
STATE · 1600 BIT
rate · 1088
capacity · 512
每次吸收后执行 24 轮 Keccak-f 置换;capacity 不直接输入或输出。
SQUEEZE 挤出摘要 从 rate 区域读取所需输出;Keccak-256 取前 256 位。
θ · theta让每一列的信息扩散到相邻列。
ρ + π旋转各 lane,并重新排列它们的位置。
χ + ι加入非线性混合与轮常量,打破对称。

最重要的命名陷阱:Keccak-256 ≠ SHA3-256

Keccak 赢得 NIST SHA-3 竞赛后,标准版 SHA3-256 采用了不同的域分离后缀。以太坊较早采用原始 Keccak-256,并沿用至今;两者内部置换相同,但同一输入会按不同的填充与域分离规则计算,摘要不可互换。以太坊文档也专门提醒这一历史命名差异。[5]

输入空串
""
Keccak-256  c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
SHA3-256    a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a
SHA-256、Ethereum Keccak-256 与 NIST SHA3-256 的家族、结构、输出、域后缀与以太坊用途对照
维度SHA-256Ethereum Keccak-256NIST SHA3-256
家族SHA-2Keccak 原始变体SHA-3 标准
结构512 位分组压缩迭代1600 位海绵状态1600 位海绵状态
输出256 位256 位256 位
域后缀不适用原始 Keccak,常见实现字节 0x01SHA-3 域分离,常见实现字节 0x06
空串开头e3b0c442…c5d24601…a7ffc6f8…
以太坊语境共识层 SSZ 等场景执行层核心哈希不能替代 Keccak-256
06

以太坊不是“只用一种哈希”,而是分层使用

执行层继承了早期以太坊对 Keccak-256 的广泛使用;PoS 共识层的 SSZ Merkle 化则把 SHA-256 定义为基础哈希。说“以太坊用 Keccak”只说对了一半。[6]

初学时先抓住一句话即可:执行层主要看 Keccak-256,共识层 SSZ 主要看 SHA-256。下面的地址、状态根与 ABI 是进阶实例;第一次阅读可以先看标题和图,再回到本课主线。

Execution layer

Keccak-256

  • 交易与区块标识:序列化字节的摘要成为可引用 ID。
  • 区块父哈希:执行区块头引用前一执行区块头。
  • 状态与存储:Merkle Patricia Trie 节点与根承诺。
  • EVM:KECCAK256 操作码供合约计算。
  • 账户与 ABI:地址派生、函数选择器、事件主题。
Consensus layer

SHA-256

  • SSZ Merkle 化:hash_tree_root 把对象承诺为 32 字节根。
  • Beacon 状态:区块、验证者与共识状态使用 SSZ 根引用。
  • Merkle 证明:用少量兄弟节点证明某字段属于给定根。
  • 协议分层:共识区块同时承载执行负载及其区块哈希。

用法一:把一大份状态压成一个可验证承诺

以太坊执行状态包含账户余额、nonce、合约代码哈希和存储。Modified Merkle Patricia Trie 先对节点做 RLP 编码:较短的子节点可以直接内联,较长的节点才以 Keccak-256 哈希引用,最终形成 32 字节状态根。节点只要独立执行交易并得到同一根,就能比较整个状态;Merkle 证明还可用少量路径节点验证某个值属于该根。[7]

未压缩公钥坐标x || y
64 bytes
Keccak-25632-byte digest
取后 20 字节0x… address

用法二:从公钥得到 20 字节地址

外部账户地址来自公钥:对未压缩公钥的 64 字节坐标部分做 Keccak-256,再取摘要最后 20 字节并加显示前缀 0x。地址因此只有 160 位;仅知道地址或公钥不会获得控制权,必须持有能生成匹配签名的私钥。[8]

用法三:用 4 字节函数选择器路由合约调用

规范函数签名 transfer(address,uint256)
Keccak-256 前 4 字节 a9059cbb2ab09eb2…

Solidity ABI 规定,标准 ABI 外部函数调用的 calldata 前 4 字节是规范函数签名 Keccak-256 的前 4 字节。[9] 这只是 32 位路由标签,碰撞远比完整 256 位摘要容易;编译器会阻止同一合约暴露相同选择器的冲突函数,安全分析也不能把选择器当唯一身份凭证。fallback 仍可以接收任意 calldata,不能把“所有调用”都简化成这一格式。

与下一课的接口:哈希怎样帮助“链起来”?

执行区块头包含前一执行区块头的 Keccak-256 哈希;改变旧区块会改变它的哈希,后继引用立刻失配。状态根、交易根与收据根又把大量数据压进区块头。[10] 本课只需记住:哈希负责暴露修改,不负责决定网络接受哪段历史。后一个问题留到下一课。

07

哈希很强,但它只解决“字节承诺”这一类问题

系统出错往往不是哈希算法被破解,而是开发者让哈希承担它从未承诺过的责任,或在进入哈希前把字节编码错了。

不是加密

摘要公开可算

哈希不保密;低熵输入可以被枚举。需要保密应使用经过审查的加密方案。

不是密码存储

短密码会被字典攻击

不要裸存 SHA-256/Keccak。应使用带随机盐、成本可调的密码 KDF,如 Argon2id、scrypt 或合适配置的 PBKDF2。

不是共识

重算哈希很便宜

哈希能暴露修改,不能单独决定哪份历史被网络接受;排序和最终性来自共识。

不是真实性

垃圾进,指纹也准确

摘要只能承诺输入字节。被哈希的天气、价格或身份信息仍可能是假的。

不是签名

任何人都能计算

哈希证明数据一致,不能证明是谁授权。身份与授权要靠私钥数字签名。

不是唯一编码

语义相同也会不同

序列化、大小写与字段边界必须先约定;哈希不会替你消除编码歧义。

进阶阅读:很多“碰撞”其实是输入编码歧义

encodePacked("a", "bc")

拼接后的字节是 "abc"

encodePacked("ab", "c")

拼接后的字节仍是 "abc"

两组参数产生相同摘要,不代表 Keccak 出现密码学碰撞;它们在进入哈希前就已被编码成相同字节。Solidity 文档因此警告:多个动态类型不应无边界地使用 abi.encodePacked 做认证;通常应优先用保留类型和边界的 abi.encode[9]

验证的完整句子

“我持有期望摘要 H,并按同一编码和算法计算数据 D,结果等于 H。”

如果期望摘要本身来自攻击者,或双方编码不同,重新计算得再准确也没有建立可信承诺。
08

把整课压缩成一条可复述的推导链

  1. 一切先变成字节哈希不理解语义;协议必须先固定编码、字段顺序与边界。
  2. 任意输入压成固定摘要SHA-256 与 Keccak-256 都输出 256 位,即 32 字节、64 个十六进制字符。
  3. 微小变化扩散到约一半输出位雪崩效应让局部篡改无法在摘要中保持局部,但它本身不是完整安全证明。
  4. 安全目标分三类理想 256 位哈希的原像与第二原像约 2²⁵⁶,碰撞因生日界约 2¹²⁸。
  5. 两种算法结构不同SHA-256 迭代压缩 512 位分组;Keccak-256 用 1600 位海绵状态吸收、置换、挤出。
  6. Keccak-256 不是 SHA3-256相同置换、不同域后缀;以太坊执行层必须使用原始 Keccak 结果。
  7. 以太坊分层使用哈希执行层大量使用 Keccak-256;共识层 SSZ Merkle 化使用 SHA-256。
  8. 哈希只承诺字节保密、授权、共识、链外真实性和安全密码存储都需要其他机制。
一口气说完

哈希是公开、确定、固定长度的字节承诺:正向易算,反向与伪造在计算上困难;它让以太坊能用很短的根与标识引用巨大状态,却不能独自创造真相或共识。

五题自测

1. 为什么 256 位哈希的通用碰撞强度通常约为 128 位?

答案 B。碰撞攻击可自由选择两边,约 2¹²⁸ 个样本就产生约 2²⁵⁶ 对比较机会。

2. Ethereum Keccak-256 与 NIST SHA3-256 的关系是什么?

答案 C。以太坊使用原始 Keccak-256;标准 SHA3-256 采用不同域分离,不能互换。

3. 下列哪句话对“雪崩效应”的描述最准确?

答案 A。雪崩描述统计扩散,不要求每个样本恰好一半,也不能单独证明抗碰撞或抗原像。

4. encodePacked("a", "bc")encodePacked("ab", "c") 得到同一摘要,最准确的解释是什么?

答案 C。哈希只看字节;如果编码丢失字段边界,不同参数组合可能先变成同一输入。

5. 为什么不应直接用 SHA-256 保存用户密码?

答案 B。哈希的原像抗性假设输入空间巨大;人类密码的候选空间远小得多。

最小术语表

Hash / 哈希
把任意长度字节串映射为固定长度摘要的函数。
Digest / 摘要
哈希函数的输出,也常称哈希值或数字指纹。
Preimage / 原像
产生某个摘要的输入;原像攻击从指定摘要反找输入。
Collision / 碰撞
两份不同输入产生相同摘要。固定长度哈希中必然存在,但应难以找到。
Avalanche / 雪崩
输入微小变化在输出中广泛扩散,理想上每个输出位约一半概率翻转。
Rate / 吸收率
海绵状态中直接吸收输入、挤出输出的部分;Keccak-256 为 1088 位。
Capacity / 容量
海绵状态中不直接暴露的安全余量;Keccak-256 为 512 位。
State root / 状态根
把整份结构化状态承诺成固定长度根哈希,支持比较和证明。
Lesson complete

哈希让一整套状态拥有一枚可复算的指纹;共识才决定网络共同承认哪一枚。

资料与延伸阅读

  1. NIST CSRC:Cryptographic hash function — 原像、第二原像与碰撞抗性的正式定义。
  2. NIST CSRC:Hash Functions — 256 位哈希的原像与碰撞安全强度。
  3. NIST FIPS 180-4:Secure Hash Standard — SHA-256 的规范与安全哈希标准。
  4. Keccak Team:The sponge construction — 海绵结构的吸收、置换与挤出。
  5. ethereum.org:The use of “SHA3” — 以太坊 Keccak 与标准 SHA-3 的历史差异。
  6. Ethereum Consensus Specs:Hashing — 共识规范将基础 hash(data) 定义为 SHA-256。
  7. ethereum.org:Merkle Patricia Trie — 执行状态、状态根与 Merkle 证明。
  8. ethereum.org:Ethereum accounts — 公钥经过 Keccak-256 并截取为地址。
  9. Solidity:Contract ABI Specification — 函数选择器及 abi.encodePacked 的歧义警告。
  10. Ethereum Execution Specs:Blocks — 执行区块父哈希与状态根的定义。
  11. NIST FIPS 202:SHA-3 Standard — SHA3-256、SHAKE 与标准化 Keccak 派生函数。