← 返回加密工具箱

HMAC 完全指南:API 签名、防篡改与时序攻击

加密工具箱 · 使用教程

对接第三方接口时,文档里常写“请对参数做 HMAC-SHA256 签名”。这个 HMAC 到底解决了什么问题?为什么不能简单地把密钥拼在数据前面算个 SHA256 完事?这篇文章讲清楚。

一、HMAC 是什么

HMAC(Hash-based Message Authentication Code,基于哈希的消息认证码)是**带密钥的哈希**。

普通哈希只能证明“数据没变”,但不能证明“数据是谁给的”——因为谁都能算哈希。

HMAC 引入了密钥:只有知道密钥的人才能算出正确的 HMAC 值。这样就能同时保证两件事:

性质含义
完整性数据被改过,HMAC 值就对不上
认证性能算出正确 HMAC 的,一定是持有密钥的一方

二、为什么不能自己拼密钥

很多人第一反应是:既然要带密钥,那直接算 SHA256(key + message) 不就行了?

**不行,这个做法有著名的“长度扩展攻击”(length extension attack)。**

原因是 SHA-256 这类 Merkle–Damgård 结构的哈希算法,其内部状态可以被延续。攻击者在**不知道密钥**的情况下,能在 SHA256(key + message) 后面追加数据,算出 SHA256(key + message + padding + 攻击者数据) 的正确值。

HMAC 通过双层哈希(内外两层,密钥参与两次)规避了这个问题:

HMAC(K, m) = H( (K' ⊕ opad) || H( (K' ⊕ ipad) || m ) )
                ↑外层哈希          ↑内层哈希

你不需要记住这个公式,只需要记住结论:**要带密钥的完整性校验,用 HMAC,不要自己拼接。**

同样地,SHA256(message + key)(密钥放后面)也不是 HMAC。虽然它抗长度扩展攻击,但仍有其他弱点,且不是标准做法。用标准库的 HMAC 函数,别自己发明。

三、典型用途

1. API 请求签名

最经典的场景。调用方和服务方共享一个密钥,每次请求:

  1. 把参数按规则拼接成字符串
  2. 用密钥算 HMAC-SHA256
  3. 把结果放在请求头或参数里(通常叫 sign、signature)

服务端用同样的方式重算一遍,比对是否一致。不一致就拒绝。

这样即使请求经过中间网络,攻击者改了参数也无法伪造签名——因为他没有密钥。

2. Webhook 校验

接收第三方回调时,怎么确认这个请求真的来自对方,而不是别人伪造的?多数平台(GitHub、Stripe、微信支付等)都在请求头里带一个 HMAC 签名。你必须校验它,否则任何人都能给你的回调接口发假消息。

不校验 Webhook 签名是一个真实存在的严重漏洞:攻击者可以伪造“支付成功”通知,白拿货。这类事故每年都有。

3. JWT 的 HS256

JWT 的 HS256 算法就是 HMAC-SHA256。头部和载荷用 Base64URL 编码后拼接,再用密钥算 HMAC 作为签名。

四、几个必须注意的点

1. 密钥长度

HMAC 的安全性依赖于密钥。规则是:**密钥长度不应短于哈希输出长度**。

HMAC-SHA256  →  密钥至少 32 字节(256 位)
HMAC-SHA512  →  密钥至少 64 字节

用 "secret" 这种 6 字符的密钥,安全性会大打折扣。

2. 比对要用恒定时间比较

普通的字符串比较是“发现第一个不同的字符就返回”。攻击者可以通过测量响应时间,一个字符一个字符地猜出正确的签名。

所以校验签名必须用**恒定时间比较**(constant-time compare):

❌ if (computed === received)        // 时序泄露
✅ crypto.timingSafeEqual(a, b)      // Node.js
✅ hmac.compare_digest(a, b)         // Python

3. 签名要覆盖所有关键字段

如果签名只覆盖了参数 A,攻击者可以随意改参数 B。设计签名规则时,**所有会影响业务逻辑的字段都必须参与**,包括时间戳、随机数(nonce)、请求路径。

4. 加时间戳防重放

HMAC 只能证明“这个请求是持有密钥的人发的”,但不能阻止攻击者把截获的请求**原样重发**。要防重放,必须加时间戳(超过 5 分钟就拒绝)和 nonce(用过就记录,不重复接受)。

五、怎么用本站工具

  1. 打开加密工具箱的 HMAC 标签页
  2. 填入密钥(建议 32 字节以上)
  3. 填入消息内容
  4. 选择 HMAC-MD5 / SHA1 / SHA256 / SHA512

计算在浏览器本地完成,密钥不会上传到任何服务器。

六、常见问题

HMAC 和加密有什么区别?
HMAC 不隐藏内容,只是生成一个校验值。数据本身仍是明文,谁都能看到。如果要保密,必须用加密(AES/SM4);如果需要既保密又可验证,就加密 + HMAC 一起用(或者用带认证的加密模式如 AES-GCM)。
HMAC 能还原出原始数据吗?
不能。它基于哈希,是单向的。HMAC 的输出只是一个固定长度的校验值。
为什么我的签名和对方算的不一致?
按概率排序:拼接规则不一致(字段顺序、分隔符、空值处理);编码不同(UTF-8 vs GBK、URL 编码与否);密钥被 Base64 解码与否;大小写(hex 输出的大小写)。API 对接失败九成出在这几项,看文档要逐字对照。
密钥该多久换一次?
看安全等级。普通业务半年到一年;涉及资金、敏感数据的场景三个月甚至更短。换密钥时要支持新旧密钥并行一段时间,否则切换瞬间所有请求都会失败。
HMAC-SHA256 和直接 SHA256 有什么区别?
SHA256 不需要密钥,谁都能算,只能校验“数据有没有损坏”;HMAC-SHA256 需要密钥,只有持钥方能算,能校验“数据是谁给的、有没有被改”。安全场景必须用后者。
前端能算 HMAC 吗?
技术上能(浏览器有 Web Crypto API),但**不该在前端放 HMAC 密钥**——前端代码和密钥是公开的,任何人打开开发者工具就能看到。API 签名必须由后端完成,或者用后端的临时凭证。