HMAC 全称 Hash-based Message Authentication Code,基于哈希的消息认证码,它不是一种哈希算法,而是利用单向哈希函数 + 密钥构造出来的消息认证机制,核心作用:同时完成【消息完整性校验】+【身份来源认证】,防篡改、防伪造。
常见场景:API 签名、Webhook 验签、JWT(HS256/HS384/HS512)、Token 校验、AWS / 阿里云 OSS 签名、消息队列防伪造、微服务接口鉴权。
一、HMAC 原理公式

符号说明:
K:密钥(secret key,双方提前共享,绝对不能在网络传输)
M:原始消息(报文、请求参数、payload)
H:底层哈希函数,常用:SHA-256、SHA-384、SHA-512;旧的 MD5、SHA1 不推荐
\(\oplus\):按位异或
ipad:内部固定填充常量
0x36重复填满一个哈希块(比如 SHA256 块大小 64 字节)opad:外部固定填充常量
0x5C重复填满哈希块\(\Vert\):字节拼接
简要执行步骤
如果密钥 K 长度 > 哈希块大小:先对 K 做一次哈希,把哈希结果作为新 K;
如果密钥 K 长度 < 块大小:末尾补 0 填充到块长度;
K 和
ipad异或 →K_ipad;K_ipad拼接原始消息 M,送入哈希 H,得到内层哈希;K 和
opad异或 →K_opad;K_opad拼接上面内层哈希,再做一次哈希 → 最终 HMAC 摘要(签名)。
✅ 关键点:相同密钥 + 相同消息 + 相同哈希算法,算出的 HMAC 一定相同;消息哪怕改 1bit,HMAC 结果完全变化。
二、HMAC 工作流程(通信双方)
假设客户端(调用方)、服务端(接收方)提前协商同一个密钥 Secret:
客户端
整理待签名原文(请求时间、url、参数、body、nonce 随机数等,顺序必须固定!)
使用 Secret + 原文计算 HMAC 值(签名)
把【原文 + HMAC 签名 + 附加防重放参数 (timestamp/nonce)】发给服务端
❗ Secret 不随请求传输!
服务端
拿到请求报文,用完全一样的规则拼接原文
使用同一个 Secret 独立计算 HMAC
安全比较:定时对比(constant-time compare)本地算出的 HMAC 和客户端传过来的 HMAC
匹配成功:认证通过;不匹配:拒绝请求
⚠️ 重点:不能直接用
==字符串比较,普通字符串比较是逐字节失败返回,会泄露差异位置,容易遭受时序攻击,必须使用恒定时间比较函数。
三、HMAC 能解决什么安全问题,不能解决什么?
✅ 能力
消息完整性:传输途中报文被篡改,HMAC 校验失败;
数据源身份认证:只有持有正确 Secret 的一方,才能生成合法 HMAC;中间人不知道密钥,无法伪造合法签名;
不需要非对称证书,计算速度快,适合高并发 API、网关验签;
支持更换底层哈希算法,向后兼容。
❌ HMAC 本身不能做到
不能加密消息:M 原文是明文传输的,HMAC 只是附加校验码;想要保密需要搭配 TLS 或者额外加密;
不自带防重放:如果没有 timestamp/nonce,中间人抓包后可以原样把请求重发一遍,服务端依然校验通过(重放攻击);
不支持不可抵赖:对称密钥双方共有,发送方可以否认是自己发的(因为接收方也能算出同样 HMAC);不可抵赖需要 RSA/ECDSA 这类非对称签名;
密钥分发难题:对称 Secret 如何安全给到双方是独立问题,HMAC 本身不解决密钥交换。
四、HMAC 和普通哈希、RSA 签名对比
一句话选型:内部系统 API、高并发接口,优先 HMAC;对外需要第三方不可抵赖场景,用非对称签名。
五、常见攻击与风险点(工程踩坑重点)
密钥泄露 HMAC 安全根基就是 Secret,一旦泄露,攻击者可以随意伪造签名;密钥必须强保密,不能硬编码在前端、客户端 JS 里(HMAC 不适合浏览器前端做签名,前端密钥极易被扒)。
签名原文拼接规则不一致 客户端和服务端参数排序、空格、换行、编码(UTF-8)不一样,会算出不同 HMAC,是业务最常见 bug。最佳实践:按字典序排序 k-v、固定序列化格式(比如 Canonical JSON)。
缺少时间戳 / 随机数 → 重放攻击 标准 HMAC 验签方案一般带上
timestamp,服务端校验时间窗口(比如 5 分钟内有效),超时直接拒绝。弱哈希算法 MD5-HMAC、SHA1-HMAC 已经不推荐,优先 HMAC-SHA256 / HMAC-SHA512。
短密钥 Secret 不能太短,密钥本身要有足够熵,推荐至少 32 字节以上随机密钥。
时序攻击(普通字符串相等判断) 语言库一般都提供安全比较方法:Java
MessageDigest.isEqual、Pythonhmac.compare_digest、Gocrypto/subtle.ConstantTimeCompare。长度扩展攻击(普通 SHA 有,HMAC 天然免疫)
这个是 HMAC 一个很重要优势:原生 HMAC 构造方式抵抗哈希长度扩展攻击;如果自己简单做
sha(secret+msg)这种简易签名,会有长度扩展漏洞,这也是为什么不要自己手写简易签名,优先标准 HMAC。 举例:很多新手错误写法sha256(secret + body),这个≠HMAC,存在安全漏洞!
六、工程最佳实践
算法:优先
HMAC-SHA256或HMAC-SHA512;密钥:密码学安全随机生成,密钥长度≥哈希输出长度;密钥轮换机制;
签名原文:使用规范化串,参数排序、去掉无关空格,统一 UTF-8;
请求要素放进签名原文:
timestamp + httpMethod + path + 排序后的query + body;防重放:timestamp + 滑动时间窗口,可选搭配一次性 nonce;
传输:签名放在请求头,如
X-HMAC-Signature、X-Timestamp;校验:一定使用恒定时间比较函数;
传输层:HMAC 建议搭配 HTTPS,双重防护,避免明文参数被监听。
七、极简代码示例(Python)
import hmac
import hashlib
import time
secret = b"this-is-a-32bytes-secure-secret-key123456"
msg = b"GET\n/api/test\ntimestamp=1762000000&name=test"
# 生成hmac签名
sig = hmac.new(secret, msg, digestmod=hashlib.sha256).hexdigest()
print(sig)
# 校验(安全对比)
verify_ok = hmac.compare_digest(sig, sig)
print(verify_ok)
八、总结
HMAC 是对称消息认证码,基于哈希 + 共享密钥,兼顾完整性和身份校验,性能高、实现简单,是接口鉴权、Webhook 验签最主流方案之一。 它的短板来自对称体系本身:密钥共享、无法抵赖、原生不防重放,需要业务层补充时间戳、随机数、密钥管理机制;同时严禁把 HMAC 密钥放在公开前端代码中。
本文原创作者:易君召,详见:https://www.yijunzhao.cn/authors/yijunzhao,转载请注明出处。
原文链接
欢迎访问 小易撩挨踢