第二章 杂凑函数基础:四大护法
要讲清楚 SM3 在密钥派生里怎么“守边界”,得先聊聊杂凑函数的老本行。说白了,它就是一把单向门——数据进去,指纹出来。你拿着指纹,永远变不回原来的数据。我当年刚入门时觉得这玩意很简单,直到在项目里踩了坑,才明白这几个性质有多重要。
2.1 单向性:为什么不能倒着走?
单向性(One-way)是杂凑函数的立身之本。意思是:给定一段消息 M,你可以轻松算出哈希值 H(M);但给你一个哈希值 h,让你找到任意一个 M 使得 H(M)=h,那几乎不可能。
我在一次安全审计中遇到过这样的场景:团队误以为可以用哈希值的部分比特作为“加密密钥”,结果被人反推出明文结构。嗯,这里要注意——单向性不等于加密性。它只是让你没法往回走,并不保证你前向走的路径是秘密的。
| 场景 | 利用单向性做什么 | 常见错误 |
|---|---|---|
| 密码存储 | 存哈希代替明文密码 | 未加盐导致彩虹表攻击 |
| 数字签名 | 先对消息哈希再签名 | 用弱哈希(如 MD5)导致伪造 |
| 文件完整性 | 发布哈希供校验 | 哈希值被篡改不验证来源 |
2.2 抗碰撞性:名字一样,人不一样?
抗碰撞性(Collision Resistance)分两层:
- 弱抗碰撞性:给定 M1,你找不到另一个 M2 使得 H(M1)=H(M2)。
- 强抗碰撞性:你找不到任意两个不同的 M1 和 M2,使得它们的哈希值相同。
直觉上看,强抗碰撞性更难防。它就是要求哈希函数不能有“双胞胎”。你想想看,如果两份不同的智能合约有相同哈希,那区块链不就乱套了?
我曾经在一家金融科技公司审核过他们的交易哈希方案。他们用的是截断后的 SHA-1(只取前 120 位)。我当时就提醒:生日攻击的代价已经低于他们的安全阈值。后来他们切到了 SM3,才放心。
2.3 抗原像性:找到原像有多难?
抗原像性(Preimage Resistance)比单向性听起来更像绕口令。其实区别很简单:
- 单向性:从输出到输入难。
- 抗原像性:给定输出,找任意一个输入使得哈希匹配也难。
还有一层叫“第二抗原像性”:给定 M1,你找不到不同的 M2 使 H(M1)=H(M2)。这其实就是弱抗碰撞性。所以这几个性质是互相嵌套的——抗碰撞性通常蕴涵抗原像性,反过来不一定。
我个人习惯在评估哈希函数时把这几条列出来逐个打钩。SM3 目前对这三条都扛得住,这也是国标选它做密钥派生的底气。
2.4 长度扩展攻击:Merkle-Damgård 的天生缺陷
这玩意是许多安全工程师的噩梦。长度扩展攻击(Length Extension Attack)只针对 Merkle-Damgård(MD)结构的哈希函数。SM3 正是基于 MD 结构。
攻击原理是这样:假设你有一段消息 M 和它的哈希 H(M)。攻击者不需要知道 M 的内容,就能算出 M 加上任意额外内容拼接后的哈希 H(M || padding || extra)。为什么?因为 MD 结构是状态链式的,最后一个状态就是哈希值。攻击者可以直接从那个状态继续算。
看个具体的:
已知:
M = "my_secret"
H(M) = SM3("my_secret")
攻击者可以计算:
M' = "my_secret" + padding + "extra_data"
H(M') = SM3(M') // 不需要知道 M,只要知道 H(M) 和 M 的长度
后果:如果系统用 SM3(M) 做身份凭证,攻击者可以用 H(M||extra) 冒充合法用户。
我曾经在 PoC 里跑过这个攻击。嗯,第一次跑通的时候真的后背发凉。那只是个本地测试,但如果是生产环境呢?所以后来我复查所有涉及哈希的协议,只要疑似用裸 MD 哈希做认证或密钥派生,一律换成 HMAC。
| 受影响的结构 | 典型算法 | 缓解手段 |
|---|---|---|
| Merkle-Damgård | MD5, SHA-1, SHA-256, SM3 | 使用 HMAC / 使用 SHA-3 / 截断输出 |
| 海绵结构 | SHA-3, Keccak, BLAKE | 天然免疫,无需额外处理 |
SM3(secret || M) 都不够,必须用 HMAC 或至少加一个独立的结构隔离步。
2.5 这一章的小结
单向性、抗碰撞性、抗原像性,这仨是哈希函数的三大基本功。长度扩展攻击则是 MD 系列特有的“暗门”。搞明白了这些,你才能理解为什么密钥派生里不能直接裸用 SM3——需要加一层“边界”。