一个看起来安全的写法,为什么会漏
很多接口早期都有过这样的校验逻辑:服务器把一段只有服务端知道的密钥拼在数据前面,对整串做 SHA-256,把结果当作“签名”发给客户端;客户端回来时带上数据和签名,服务器重算一遍比对。看起来密钥没泄露、哈希又不可逆,应该万无一失。问题出在一个反直觉的性质上:对 SHA-256、SHA-1、MD5 这类哈希来说,知道哈希值本身,就等于知道了一台“可以继续往前算的机器”的内部状态。
机制:中间状态就是输出
这类哈希采用 Merkle–Damgård 结构:把消息切成固定长度的块,逐块喂给一个压缩函数,函数不断滚动更新一个固定长度的内部状态,最后一块算完,这个状态直接对外输出,就是哈希值。换句话说,输出不是对全文的某种“整体指纹”,而是喂完所有块之后的寄存器快照。
于是攻击者可以做这样的事。他不知道原始消息,但知道原消息的哈希值和长度。他把自己构造的尾巴接在“原文后面”,前面补上按原消息长度算出的填充位,然后从原哈希值这个状态出发继续往下压缩。压缩函数并不知道也不关心之前喂的是不是同一批消息——它只认状态。继续算完,得到的正是“原文+填充+尾巴”的合法哈希。这就是长度扩展攻击,和哈希函数“能不能逆推原文”没有关系,是结构暴露了中间状态。
在协议里会造成什么后果
后果取决于开发者拿哈希当什么用。如果只是想校验文件完整性,长度扩展基本无害。危险出现在“把哈希当消息认证码”的场合:假设服务器接受一个参数,校验值是 SHA256(secret‖data),攻击者虽然改不了 data 的内容,却能提交一个更长的 data′,它的前段仍是原数据、后段是攻击者加的尾巴,并附上推算出的新校验值。如果服务器的解析器对重复字段取后值或前值,多出来的字段就可能覆盖关键字段。早年公开讨论过的 Flickr 接口登录绕过事件,就是这类“先哈希拼接再校验”的设计被长度扩展利用的案例,具体细节以当年的技术披露原文为准。
正确的做法:HMAC 与哈希选择的边界
终结这个问题的办法不是“把密钥拼得更巧妙”,而是换构造。第一种是 HMAC:它把密钥用两个不同的填充分别混进内外两次哈希(H(key⊕opad ‖ H(key⊕ipad ‖ message))),外层哈希的输入不再是攻击者能从输出反推的状态,长度扩展这条路就断了。HMAC 定义在 RFC 2104,不需要换底层哈希,SHA-256 之上照样能用。第二种是换哈希:SHA-3 采用海绵结构,输出的不是继续计算的完整状态(只要截断输出长度足够留有余量),天然不携带这个性质;盲签名、区块链里常用的 Blake 系列同样如此。
一条实用的判断线:凡是要“证明一段数据没有被篡改过、且确实来自持密钥方”的需求,直接上 HMAC 或 Ed25519 这类签名算法,永远不要用 hash(secret ‖ message) 这种手写拼接。看到别人代码里出现 sha256(secret + data),不管密钥拼在前面还是后面,都按有问题处理。
快速问答
问:SHA-256 本身被攻破了吗? 没有。长度扩展不是“破解哈希”,抗碰撞、抗原像这两个性质都不受影响,它只说明哈希输出可以当状态继续用,而这个用途本不该拿来做消息认证。
问:拼在末尾行不行?
也不行。hash(message ‖ secret) 虽然不能直接扩展,但会引入其他攻击面,而且同样不满足认证码的安全定义。规范答案还是 HMAC。
问:区块链项目里常见吗? 早期钱包和交易所 API 签名方案里偶尔能见到手写拼接,主流方案基本都收敛到 HMAC 或非对称签名。审合约或审接口文档时,这一项值得专门看一眼。
风险提示:本文是密码学机制科普,不构成任何安全审计结论或投资建议;实现层面的缺陷请以正规安全审计为准。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。