首页/安全/加密与证书速查/HMAC 消息认证码
加密算法基础95 条

HMAC 消息认证码

RFC 2104 / FIPS 198-1 定义的密钥化认证码:H((K⊕opad) || H((K⊕ipad) || M)),用于验证消息完整性与来源,广泛用于 API 签名与 JWT。

加密与证书速查安全更新于 2026-10-13

HMAC 是什么

HMAC(Hash-based Message Authentication Code)是 RFC 2104 与 FIPS 198-1 定义的密钥化认证码,用带密钥的哈希同时提供消息完整性与来源认证:只有持有同一密钥的一方才能算出相同的值。它不加密内容,明文照常可见;作用是让对方确认消息没被改、且确实来自持有密钥的伙伴。

基本信息

标准RFC 2104 / FIPS 198-1
构造H((K xor opad) || H((K xor ipad) || M))
推荐哈希SHA-256 及以上
密钥长度不小于哈希输出长度,如 32 字节随机值
输出长度与底层哈希一致(SHA-256 为 32 字节)
典型用途接口签名、Webhook 校验、JWT HS256、TLS 1.2 的 PRF

典型使用场景

  • 开放平台接口签名:把参数按约定顺序拼接后计算 HMAC,服务端复算比对
  • Webhook 回调验签:第三方推送事件时附带签名头,接收方用共享密钥校验来源与完整性
  • JWT 的 HS256:用对称密钥签令牌,适合前后端或服务之间自用
  • 会话与 Cookie 完整性:对状态值加签名,防止客户端篡改字段
  • 文件传输校验:共享密钥场景下比裸哈希更能防主动篡改
  • 消息队列与内部 RPC:为消息体附加签名,避免伪造与重放(重放还需时间戳与序号配合)

计算与校验

计算与校验
# 命令行计算 HMAC(-hmac 后面跟共享密钥)
printf '%s' 'payload' | openssl dgst -sha256 -hmac 'my-secret'
# 校验时把收到的签名与本地复算值做比较(生产代码用常量时间比较)
printf '%s' 'payload' | openssl dgst -sha256 -hmac 'my-secret' | awk '{print $2}'
# Base64 形式便于放进 Header,注意区分二进制签名与十六进制字符串
printf '%s' 'payload' | openssl dgst -sha256 -hmac 'my-secret' -binary | openssl base64
# 生成一把 32 字节随机密钥,禁止用口令或短字符串充当
openssl rand -base64 32

服务端校验(Python)

服务端校验(Python)
import hmac, hashlib
# 常量时间比较:compare_digest 避免逐字节比较泄漏时序信息
signature = request.headers['X-Signature']              # 十六进制字符串
expected = hmac.new(secret, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, expected):
    return 401                                           # 统一错误响应,不提示差异细节
# 建议把时间戳一起纳入签名,并限制时间窗口,防重放攻击

与相邻概念对照

机制是否有密钥保护目标典型误用
裸哈希(SHA-256)无仅完整性提示当作签名做来源校验,攻击者可重算
HMAC共享密钥完整性与来源密钥过短、比较非常量时间
数字签名私钥/公钥完整性与不可否认来源私钥权限过宽、填充混用
AEAD 加密密钥机密性与完整性nonce 重用、只看密文不看标签
校验 HMAC 必须使用常量时间比较(Python 的 hmac.compare_digest、Go 的 hmac.Equal 等)。逐字节提前返回的比较方式会泄漏「前多少字节匹配」,理论上可被逐字节恢复出合法签名。
HMAC 的密钥不能是口令或短字符串:密钥熵不足时,攻击者可用字典离线复算。用密码学随机数生成至少 32 字节,并像对待密码一样存放(配置中心、密钥管理服务),不写死在代码与镜像里。
HMAC 只解决「消息没被改、来源可信」,不解决重放。完整方案要叠加时间戳与随机数(nonce),服务端校验时间窗口并记录已用过的 nonce。签名内容里也要包含时间戳与请求路径,避免把同一签名挪用到其他接口。

签名串构造建议

要素是否纳入原因
HTTP 方法是防止同一路径的读写请求互相顶替
请求路径与查询串是防止签名被挪用到其他接口
请求体摘要是大请求体可只签摘要,避免整体参与计算
时间戳是配合服务端时间窗口判断,降低重放可能
随机数 nonce建议挡住窗口期内的重复提交,需服务端做去重
密钥标识 keyid建议支持密钥轮换,避免只能靠时间推断用哪把密钥

易错点

  • 用哈希代替 HMAC:摘要无密钥,任何人可重算,来源校验形同虚设
  • 比较签名时用了普通字符串相等或提前返回:存在时序侧信道,长签名可被逐字节试探
  • 密钥过短或直接使用口令:等效于把安全强度降到字典规模,离线爆破成本极低
  • 签名不覆盖时间戳与请求路径:同一签名可以跨接口或长时间重复使用,形成重放漏洞
  • 把 HMAC 与加密混为一谈:HMAC 不隐藏内容,需要保密必须另做加密(并注意 Encrypt-then-MAC 的顺序)

数据来源与参考

  • RFC 2104(HMAC)
  • FIPS 198-1(HMAC 标准)
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌