哈希不是压缩文件
普通压缩需要可逆;哈希故意丢弃海量信息,只保留固定长度承诺。
Ethereum learning path · Chapter 02 / Lesson 03
第二章 · 第三课 · Hash functions
哈希把任意长度的数据压成固定长度的“数字指纹”。它不能加密秘密,却是交易标识、区块关联、状态承诺、地址与合约调用的基础。
256 位 = 32 字节 = 64 个十六进制字符。前缀 0x 只是显示惯例,不计入摘要本身。
先忘掉区块链。把密码学哈希想成一台公开、无密钥、每个人都能重复运行的指纹机:输入任意字节,输出固定长度摘要。
文本、图片、程序、交易或整棵数据树,进入哈希函数前都必须先变成字节。函数看不懂“以太坊”是什么意思,也不知道两份 JSON 在语义上是否等价;它只认每一位是 0 还是 1。
因此,同一个“意思”如果编码不同,摘要就可能完全不同:大小写、空格、换行、Unicode 规范化、字段顺序和序列化方式都属于输入的一部分。区块链协议必须先规定唯一的编码规则,哈希才有共同答案。
普通压缩需要可逆;哈希故意丢弃海量信息,只保留固定长度承诺。
没有密钥,也没有“解密”操作。任何人都能计算同一输入的摘要。
输出看起来杂乱,但同一算法与同一字节输入永远得到同一结果。
它能支持字节完整性校验,不能证明输入描述的链外事件真实。
“输出 256 位”只是格式。真正让哈希可用于安全系统的,是确定性、快速计算、雪崩扩散,以及三类难以逆向或伪造的安全目标。NIST 将原像、第二原像与碰撞抗性列为密码学哈希的核心性质。[1]
任何机器在同一算法上处理同一字节串,必须得到完全相同的摘要。
空字符串与一整部电影,经 SHA-256 或 Keccak-256 都输出 256 位。
验证者需要迅速复算摘要;安全来自反向问题很难,而非正向计算很慢。
输入只变 1 位,输出每一位都应近似以 50% 概率翻转,变化迅速扩散。
给定摘要很难找出任意输入;给定一份输入也很难找出同摘要的另一份。
很难主动找到任意两份不同输入,使它们产生完全相同的摘要。
雪崩效应让输出“看起来充分混合”,但仅凭雪崩现象,不能证明算法抗碰撞或抗逆向。
一个设计很差的函数也可能让许多位变化;密码学安全还需要公开分析、标准化与长期密码分析检验。ethereum 与 Ethereum 的 UTF-8 编码只差一个比特:小写 e = 0x65,大写 E = 0x45,差值正是 0x20。
编辑任意输入;紫色十六进制位与方格表示两份摘要的差异。
541111248b45b7a8dc3f5579f630e74cb01456ea6ac067d3f4d793245a255155
564ccaf7594d66b1eaaea24fe01f0585bf52ee70852af4eac0cc4b04711cd0e2
等待本地算法自检;静态示例不依赖脚本运行。
静态示例:Keccak-256 下,两份仅差 1 个输入比特的数据有 126 / 256 个输出比特不同。当前环境尚未完成互动计算自检。
“理想上约一半”是统计期望,不是每次都必须恰好 128 位。上面的 Keccak-256 示例变化 126 位;切换 SHA-256,同一对输入变化 131 位。单次结果偏离 128 很正常。
雪崩效应带来两项直观价值:数据被改动时,摘要几乎不可能只“小改一点”;观察摘要也看不出输入之间是否相似。但请记住,相似度消失不等于内容被隐藏——如果输入候选很少,攻击者仍可逐个猜测并比对。
安全不是“绝对做不到”,而是在当前算法、算力与时间尺度下计算上不可行。对于行为理想的 256 位哈希,不同目标需要的工作量不同。[2]
给定 y,找 x:H(x) = y
只看到某个摘要,尝试找出任意能产生它的输入。理想 256 位哈希约需 2256 级工作。
给定 x,找 x′ ≠ x:H(x′) = H(x)
已有一份合法文件,试图制造另一份同摘要文件。一般目标强度接近 256 位。
找任意 x ≠ x′:H(x) = H(x′)
攻击者可同时挑两边,因生日悖论只需约 2128 级尝试,而不是 2256。
无限输入 → 只有 2²⁵⁶ 个输出
抽屉原理决定不同输入必会共享摘要;“抗碰撞”只承诺难以找到,不承诺不存在。
如果只指定一个摘要,你像在 2256 个格子里找某一个格子;如果只要求任意两次落进同一格,样本之间可以两两配对。大约取到 2128 个随机样本时,配对数已经接近 2256,碰撞概率开始变得显著。这就是生日界。
2128 依然巨大;但“256 位输出”不能被简单叫作“256 位碰撞安全”。
这里描述理想通用攻击的数量级。具体算法的真实强度还取决于密码分析、实现错误、输出截断与协议怎样使用摘要。SHA-256 属于 SHA-2 家族,由 NIST 的 FIPS 180-4 规定。它不是把整份消息一次扔进黑箱,而是预处理后分块,以八个 32 位工作字为内部状态,重复压缩。[3]
Merkle–Damgård 式迭代结构 · 512 位消息分组 · 64 轮压缩 · 256 位输出
Σ / σ不同距离的循环右移与右移,让每一位影响多个位置。Ch / Maj按位选择与多数函数,以非线性方式混合工作字。mod 2³²32 位模加法丢弃进位,配合异或和旋转形成扩散。每个分组的输出会成为下一个分组的输入状态,因此消息前面的一点变化会一路传到最终摘要。正向运行很快,但从最终八个状态字反推所有分组,在设计目标下不可行。
这类迭代结构具有经典的长度扩展性质:拿到 SHA256(secret || message) 后,只要知道或猜中 secret || message 的总长度,攻击者就可能从旧摘要继续内部状态,为 message || glue_padding || attacker_suffix 构造有效摘要,而无需知道秘密本身;未知密钥长度通常还可以枚举。因此需要消息认证时应使用经过证明的 HMAC-SHA-256,不要自创“秘密前缀 + 哈希”协议。Keccak 海绵不受这一经典 Merkle–Damgård 长度扩展方式影响,但认证仍应使用标准 MAC 或数字签名方案。
Keccak 采用海绵结构:内部维护 1600 位状态,把输入分块“吸收”进可见区域,每块之间执行 Keccak-f[1600] 置换;吸收完再从状态中“挤出”摘要。[4]
Sponge 海绵结构 · rate 1088 位 · capacity 512 位 · 24 轮置换 · 256 位输出
θ · theta让每一列的信息扩散到相邻列。ρ + π旋转各 lane,并重新排列它们的位置。χ + ι加入非线性混合与轮常量,打破对称。Keccak 赢得 NIST SHA-3 竞赛后,标准版 SHA3-256 采用了不同的域分离后缀。以太坊较早采用原始 Keccak-256,并沿用至今;两者内部置换相同,但同一输入会按不同的填充与域分离规则计算,摘要不可互换。以太坊文档也专门提醒这一历史命名差异。[5]
""
Keccak-256 c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
SHA3-256 a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a
| 维度 | SHA-256 | Ethereum Keccak-256 | NIST SHA3-256 |
|---|---|---|---|
| 家族 | SHA-2 | Keccak 原始变体 | SHA-3 标准 |
| 结构 | 512 位分组压缩迭代 | 1600 位海绵状态 | 1600 位海绵状态 |
| 输出 | 256 位 | 256 位 | 256 位 |
| 域后缀 | 不适用 | 原始 Keccak,常见实现字节 0x01 | SHA-3 域分离,常见实现字节 0x06 |
| 空串开头 | e3b0c442… | c5d24601… | a7ffc6f8… |
| 以太坊语境 | 共识层 SSZ 等场景 | 执行层核心哈希 | 不能替代 Keccak-256 |
执行层继承了早期以太坊对 Keccak-256 的广泛使用;PoS 共识层的 SSZ Merkle 化则把 SHA-256 定义为基础哈希。说“以太坊用 Keccak”只说对了一半。[6]
初学时先抓住一句话即可:执行层主要看 Keccak-256,共识层 SSZ 主要看 SHA-256。下面的地址、状态根与 ABI 是进阶实例;第一次阅读可以先看标题和图,再回到本课主线。
KECCAK256 操作码供合约计算。hash_tree_root 把对象承诺为 32 字节根。以太坊执行状态包含账户余额、nonce、合约代码哈希和存储。Modified Merkle Patricia Trie 先对节点做 RLP 编码:较短的子节点可以直接内联,较长的节点才以 Keccak-256 哈希引用,最终形成 32 字节状态根。节点只要独立执行交易并得到同一根,就能比较整个状态;Merkle 证明还可用少量路径节点验证某个值属于该根。[7]
外部账户地址来自公钥:对未压缩公钥的 64 字节坐标部分做 Keccak-256,再取摘要最后 20 字节并加显示前缀 0x。地址因此只有 160 位;仅知道地址或公钥不会获得控制权,必须持有能生成匹配签名的私钥。[8]
transfer(address,uint256)
a9059cbb2ab09eb2…
Solidity ABI 规定,标准 ABI 外部函数调用的 calldata 前 4 字节是规范函数签名 Keccak-256 的前 4 字节。[9] 这只是 32 位路由标签,碰撞远比完整 256 位摘要容易;编译器会阻止同一合约暴露相同选择器的冲突函数,安全分析也不能把选择器当唯一身份凭证。fallback 仍可以接收任意 calldata,不能把“所有调用”都简化成这一格式。
执行区块头包含前一执行区块头的 Keccak-256 哈希;改变旧区块会改变它的哈希,后继引用立刻失配。状态根、交易根与收据根又把大量数据压进区块头。[10] 本课只需记住:哈希负责暴露修改,不负责决定网络接受哪段历史。后一个问题留到下一课。
系统出错往往不是哈希算法被破解,而是开发者让哈希承担它从未承诺过的责任,或在进入哈希前把字节编码错了。
哈希不保密;低熵输入可以被枚举。需要保密应使用经过审查的加密方案。
不要裸存 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。”
如果期望摘要本身来自攻击者,或双方编码不同,重新计算得再准确也没有建立可信承诺。哈希是公开、确定、固定长度的字节承诺:正向易算,反向与伪造在计算上困难;它让以太坊能用很短的根与标识引用巨大状态,却不能独自创造真相或共识。
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(data) 定义为 SHA-256。abi.encodePacked 的歧义警告。