HMAC、PBKDF2 与 HKDF —— 从零讲清带密钥哈希
一句话:像 SHA-256 这样的裸哈希谁都能算,所以它证明不了消息是谁发的 —— HMAC 混入一个秘密密钥,使只有持有密钥的人才能产生(或验证)摘要;PBKDF2 和 HKDF 则复用同一套 HMAC 机器,把秘密变成密钥:对口令要慢,对已随机的材料要快。
HMAC 是什么(RFC 2104)
构造。HMAC 是两次哈希,不是一次。密钥被填充到哈希的块大小(MD5/SHA-1/SHA-256 为 64 字节,SHA-384/512 为 128 字节);消息先与“密钥 XOR 固定内层填充”一起哈希一次,得到的摘要再与“密钥 XOR 固定外层填充”哈希一次。只要底层哈希是像样的伪随机函数,这个双夹层就被证明是安全的 —— 这也是 HMAC-MD5 至今仍能抵御攻破裸 MD5 的实用碰撞攻击的原因(尽管它反正已经退役,见下文)。
密钥。任意长度都可以:短于块大小的直接使用(但不足 32 字节会被人皱眉),更长的会先被哈希。本页允许把密钥按文本、hex 或 Base64 输入,这样 64 个 hex 字符的生产密钥不会被误当 64 字符的文本口令粘贴 —— 输出行里记录读取方式正是为了这个。
在哪里会遇到它。JWT 头部的 HS256 字段就是对 base64url 编码的令牌部分做 HMAC-SHA-256(粘贴 HS256 密钥时,本站的JWT 解码器算的正是它)。Webhook 签名(Stripe、GitHub)、AWS SigV4、TLS 记录保护(旧版本)以及 OpenSSH 的近亲 umac-64@openssh.com 都属于同一家族。
为什么不直接 hash(message || key)?长度扩展攻击:MD5/SHA-1/SHA-2 家族的每种哈希都允许知道 H(secret || msg) 的攻击者在没有秘密的情况下算出 H(secret || msg || padding || anything)。HMAC 的内外分层消灭了这一类攻击。这是 API 签名代码里最常见的 DIY 错误 —— 也是 HMAC 存在的理由。
PBKDF2 是什么(RFC 8018)
慢是设计出来的。一个口令可能只有 40 比特的真实熵;攻击者的 GPU 每秒能试数十亿次裸 SHA-256。PBKDF2 把 HMAC(password, salt) 反复运行 —— iter 次 —— 让你和攻击者每次猜测付出同样的 iter 次 HMAC。这里的默认值是 HMAC-SHA-256 的 600,000 次迭代,即 OWASP 口令存储速查表的基线;滑块上限一千万,因为随着硬件提速,安全建议也会不断上调。
盐。每口令的随机盐让相同口令的用户不再哈希出相同结果,预计算表(彩虹表)也就失效了。盐不是秘密 —— 它就存在哈希旁边 —— 它只需要唯一。16 个随机字节是惯例。
已知弱点。PBKDF2 抗 CPU 但不抗内存:GPU 和 ASIC 并行化它毫不费力。所以在设计口令哈希存储时,现代建议是 scrypt 或 Argon2id(本家族的密码哈希生成器页面覆盖了它们),而 PBKDF2 仍是兼容性与 FIPS-140 场景的正确答案。
HKDF 是什么(RFC 5869)
先提取,再扩展。HKDF 做两件事。Extract:把可能不均匀的输入密钥材料(例如 Diffie–Hellman 共享秘密)与盐一起压成一个短的伪随机密钥 PRK = HMAC(salt, IKM)。Expand:把 PRK 拉伸到最多 255 个哈希块的任意长度,每个输出块与前一块链式相连并由 info 字段标记用途。结果是一个秘密可以安全地派生出加密密钥、MAC 密钥和 IV,彼此分离。
不适用于口令。HKDF 假定输入已有充分熵 —— 它每 32 个输出字节只做一次 HMAC,人类口令几乎瞬间穿过它,这正是攻击者想要的。用 HKDF 而不是 PBKDF2 处理口令是经典设计错误;info 字段是用途标签("client traffic key"、"server iv"),不是额外熵。
在哪里会遇到它。TLS 1.3 从握手秘密经 HKDF 派生它使用的每一个密钥;WireGuard、Signal、EAP-TLS 1.3 和 JSON Web Encryption(作为直接密钥协商 KDF)都在同一棵家族树上。
锚点值 —— 在本页复现它们
前两张表是本页样例按钮和自测所用的测试向量(密钥 "key"、消息“The quick brown fox jumps over the lazy dog”;然后是 RFC 风格的 16 字节 0x0b 密钥与消息“Hi There”)。全部由独立原生实现生成,并在本页复核。
“fox” 的 HMAC,密钥 = key | 摘要(hex) |
| HMAC-MD5 | 80070713463e7749b90c2dc24911e275 |
| HMAC-SHA-1 | de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9 |
| HMAC-SHA-256 | f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8 |
| HMAC-SHA-384 | d7f4727e2c0b39ae0f1e40cc96f60242d5b7801841cea6fc592c5d3e1ae50700582a96cf35e1e554995fe4e03381c237 |
| HMAC-SHA-512 | b42af09057bac1e2d41708e48a902e09b5ff7f12ab428a4fe86653c73dd248fb82f948a549f7b791a5b41915ee4d1ec3935357e4e2317250d0372afa2ebeeb3a |
| KDF 锚点 | 参数 | 输出(hex,首字节) |
| PBKDF2-HMAC-SHA-1 | password / salt,c=4096,32 字节 | 4b007901b765489abead49d926f721d065a429c12e463f6c4cd79401085b03db |
| PBKDF2-HMAC-SHA-256 | password / salt,c=4096,32 字节 | c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a |
| HKDF-SHA-256 (RFC 5869 A.1) | IKM=0x0b×22,盐=00..0c,info=f0..f9,L=42 | 3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865 |
| HKDF-SHA-256 (RFC 5869 A.2) | IKM=00..4f,盐=60..af,info=b0..ff,L=82 | b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c… |
| HKDF-SHA-256 (RFC 5869 A.3) | IKM=0x0b×22,空盐与空 info,L=42 | 8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8 |
常见误区
- 把 HMAC 当加密。HMAC 证明完整性与来源(给定共享秘密);它不隐藏任何东西。没有密钥的人仍然能看到完整消息。
- 把 HMAC 当签名。数字签名是公钥的:一人签名,任何人可验证。HMAC 是对称的:每个验证者同样能伪造。不可抵赖需要 RSA/ECDSA(本家族的 RSA 工具发布后可用),不是 HMAC。
- 密钥读取方式不匹配。把 64 字符的 hex 串按文本粘贴进密钥框会变成 64 字节而不是 32 —— HMAC 完全不同。本页在输出行打印读取方式;两个工具结果不一致时,先比这一项。
- PBKDF2 迭代次数太低。2000 年时 1,000 次还算合理;今天的 GPU 集群秒杀它。如果你的系统还在存 1,000 次迭代的哈希,规划一次“登录时重哈希”的迁移吧。
- 对口令用 HKDF。每个输出块一次 HMAC 意味着口令瞬间通过 —— 口令用 PBKDF2(或 Argon2id),高熵秘密才用 HKDF。
- 一个派生密钥处处用。修复是免费的:用不同的
info 标签派生独立的密钥。同一 IKM 不同标签派生的两个密钥在设计上就是独立的。
常见问题
HMAC-MD5 被破了吗?作为抗碰撞哈希,MD5 已经死了,但 HMAC-MD5 的安全性取决于 MD5 作为伪随机函数的表现,尚无已知实用破解。反正也退役了 —— 除非为了兼容旧协议,没有理由在 HMAC-SHA-256 可用时选它。
HMAC 密钥该多长?至少等于哈希输出长度(SHA-256 为 32 字节),并由 CSPRNG 生成。短于摘要长度的密钥允许使用,但每一位不足都直接降低安全性。
PBKDF2 迭代越多就越“安全”吗,可以无限加?它线性提高攻击者的成本,但也线性提高你的登录成本。博弈在于选择用户能容忍的最大次数 —— OWASP 的数字就是按这个校准的。
为什么 HKDF 上限是 255 × 哈希长度?扩展循环是 T(i) = HMAC(PRK, T(i-1) || info || i),计数器 i 只有一个字节 —— 255 是最大计数值,这是 RFC 5869 的硬性结构限制,不是策略选择。
本页的 PBKDF2 和密码哈希生成器有什么区别?同一算法,不同工作。本页派生原始密钥字节供你在别处使用;密码哈希页生成的是存储凭据字符串(内嵌盐与参数),并对照它们验证粘贴的候选口令。