那个失败的显而易见的想法

你想证明一条消息来自持有共享密钥的某人,且未被篡改。直觉的做法是把密钥与消息一起哈希:tag = SHA256(密钥 + 消息)。它看似稳固,标签依赖于密钥,因此不知道密钥的攻击者无法产生它。遗憾的是,对最常见的哈希函数而言,这一构造已被攻破。

长度扩展攻击

Merkle-Damgård 家族的哈希(包括 -1 和 SHA-256)按块处理输入并携带一个内部状态。它们的最终输出就是那个内部状态。这会泄露危险的东西:给定 SHA256(密钥 + 消息) 以及 密钥 + 消息 的长度,攻击者可以把哈希的内部状态设为你的标签并继续哈希,计算出

SHA256(密钥 + 消息 + 填充 + 攻击者数据)

作为一个扩展后消息的有效标签,而始终不知道密钥。他可以追加数据并产生一个会通过验证的标签。对于一个消息是一组已签名参数的 API,这可能意味着向一个请求追加 &admin=true 却仍能通过签名校验。朴素的 hash(密钥 + 消息) 并不安全。

HMAC 如何修复它

(RFC 2104)不仅仅是拼接。它以两种不同方式混入密钥并哈希两次:

HMAC(K, m) = H( (K ⊕ opad) || H( (K ⊕ ipad) || m ) )

消息在一个内部填充(ipad)下与密钥一起哈希,该结果再在一个外部填充(opad)下与密钥一起哈希。由于外层哈希包裹了内层哈希,攻击者看到的值不是一个他可以扩展的原始内部状态;它是用密钥进行第二次哈希步骤的输出。长度扩展不再奏效,而 HMAC 的安全性有一个坚实的证明,仅依赖于底层哈希是合理的。

因此每个成熟系统都使用 HMAC(或另一种合适的 MAC),而非自制的带密钥哈希。请注意 SHA-3 和 BLAKE 不易受长度扩展影响,因此可以更直接地加入密钥,但 HMAC 仍是可移植且被广泛支持的标准。

教训

使用一个强哈希(HMAC-SHA256 是常见默认)以及一个高熵密钥来用 HMAC。不要发明你自己的带密钥哈希方案:缺陷从外部看并不明显,这正是它危险之处。

HMAC 工具 可对一条消息和一个密钥计算 HMAC-SHA256 及相关变体,便于你查看并比较标签,全部在你的浏览器中,不发送任何内容。