易君召
易君召
发布于 2026-11-05 / 5 阅读
0

HMAC 认证介绍与深度分析

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\):字节拼接

简要执行步骤

  1. 如果密钥 K 长度 > 哈希块大小:先对 K 做一次哈希,把哈希结果作为新 K;

  2. 如果密钥 K 长度 < 块大小:末尾补 0 填充到块长度;

  3. K 和 ipad 异或 → K_ipad;

  4. K_ipad 拼接原始消息 M,送入哈希 H,得到内层哈希;

  5. K 和 opad 异或 → K_opad;

  6. K_opad 拼接上面内层哈希,再做一次哈希 → 最终 HMAC 摘要(签名)。

✅ 关键点:相同密钥 + 相同消息 + 相同哈希算法,算出的 HMAC 一定相同;消息哪怕改 1bit,HMAC 结果完全变化。

二、HMAC 工作流程(通信双方)

假设客户端(调用方)、服务端(接收方)提前协商同一个密钥 Secret:

  1. 客户端

    • 整理待签名原文(请求时间、url、参数、body、nonce 随机数等,顺序必须固定!)

    • 使用 Secret + 原文计算 HMAC 值(签名)

    • 把【原文 + HMAC 签名 + 附加防重放参数 (timestamp/nonce)】发给服务端

    • ❗ Secret 不随请求传输!

  2. 服务端

    • 拿到请求报文,用完全一样的规则拼接原文

    • 使用同一个 Secret 独立计算 HMAC

    • 安全比较:定时对比(constant-time compare)本地算出的 HMAC 和客户端传过来的 HMAC

    • 匹配成功:认证通过;不匹配:拒绝请求

⚠️ 重点:不能直接用 == 字符串比较,普通字符串比较是逐字节失败返回,会泄露差异位置,容易遭受时序攻击,必须使用恒定时间比较函数。

三、HMAC 能解决什么安全问题,不能解决什么?

✅ 能力

  1. 消息完整性:传输途中报文被篡改,HMAC 校验失败;

  2. 数据源身份认证:只有持有正确 Secret 的一方,才能生成合法 HMAC;中间人不知道密钥,无法伪造合法签名;

  3. 不需要非对称证书,计算速度快,适合高并发 API、网关验签;

  4. 支持更换底层哈希算法,向后兼容。

❌ HMAC 本身不能做到

  1. 不能加密消息:M 原文是明文传输的,HMAC 只是附加校验码;想要保密需要搭配 TLS 或者额外加密;

  2. 不自带防重放:如果没有 timestamp/nonce,中间人抓包后可以原样把请求重发一遍,服务端依然校验通过(重放攻击);

  3. 不支持不可抵赖:对称密钥双方共有,发送方可以否认是自己发的(因为接收方也能算出同样 HMAC);不可抵赖需要 RSA/ECDSA 这类非对称签名;

  4. 密钥分发难题:对称 Secret 如何安全给到双方是独立问题,HMAC 本身不解决密钥交换。

四、HMAC 和普通哈希、RSA 签名对比

方案

类型

密钥

防篡改

身份认证

防抵赖

性能

单纯 Hash (MD5/SHA256)

单向哈希

无

✅

❌,任何人都能计算

❌

极快

HMAC

带密钥哈希(对称)

共享密钥

✅

✅

❌

快

RSA/ECDSA 签名

非对称签名

私钥签名、公钥验签

✅

✅

✅

较慢

一句话选型:内部系统 API、高并发接口,优先 HMAC;对外需要第三方不可抵赖场景,用非对称签名。

五、常见攻击与风险点(工程踩坑重点)

  1. 密钥泄露 HMAC 安全根基就是 Secret,一旦泄露,攻击者可以随意伪造签名;密钥必须强保密,不能硬编码在前端、客户端 JS 里(HMAC 不适合浏览器前端做签名,前端密钥极易被扒)。

  2. 签名原文拼接规则不一致 客户端和服务端参数排序、空格、换行、编码(UTF-8)不一样,会算出不同 HMAC,是业务最常见 bug。最佳实践:按字典序排序 k-v、固定序列化格式(比如 Canonical JSON)。

  3. 缺少时间戳 / 随机数 → 重放攻击 标准 HMAC 验签方案一般带上 timestamp,服务端校验时间窗口(比如 5 分钟内有效),超时直接拒绝。

  4. 弱哈希算法 MD5-HMAC、SHA1-HMAC 已经不推荐,优先 HMAC-SHA256 / HMAC-SHA512。

  5. 短密钥 Secret 不能太短,密钥本身要有足够熵,推荐至少 32 字节以上随机密钥。

  6. 时序攻击(普通字符串相等判断) 语言库一般都提供安全比较方法:Java MessageDigest.isEqual、Python hmac.compare_digest、Go crypto/subtle.ConstantTimeCompare。

  7. 长度扩展攻击(普通 SHA 有,HMAC 天然免疫)

    这个是 HMAC 一个很重要优势:原生 HMAC 构造方式抵抗哈希长度扩展攻击;如果自己简单做 sha(secret+msg) 这种简易签名,会有长度扩展漏洞,这也是为什么不要自己手写简易签名,优先标准 HMAC。 举例:很多新手错误写法 sha256(secret + body),这个≠HMAC,存在安全漏洞!

六、工程最佳实践

  1. 算法:优先 HMAC-SHA256 或 HMAC-SHA512;

  2. 密钥:密码学安全随机生成,密钥长度≥哈希输出长度;密钥轮换机制;

  3. 签名原文:使用规范化串,参数排序、去掉无关空格,统一 UTF-8;

  4. 请求要素放进签名原文:timestamp + httpMethod + path + 排序后的query + body;

  5. 防重放:timestamp + 滑动时间窗口,可选搭配一次性 nonce;

  6. 传输:签名放在请求头,如 X-HMAC-Signature、X-Timestamp;

  7. 校验:一定使用恒定时间比较函数;

  8. 传输层: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,转载请注明出处。

原文链接 https://www.yijunzhao.cn/archives/hmac-authentication-introduction-deep-analysis

欢迎访问 小易撩挨踢

https://www.yijunzhao.cn/