HMAC 与拼接写法的区别
HMAC 是 RFC 2104 定义的密钥化哈希构造,内部由两层组成:内层把密钥与 ipad 异或后哈希消息,外层再用 opad 做一次哈希。之所以不能直接用「哈希(密钥 加 消息)」来校验消息来源,是因为 MD5、SHA-1、SHA-256 这类迭代哈希存在长度扩展性质,攻击者可以在已知摘要的前提下追加数据并算出合法摘要。HMAC 的双层结构正好阻断这一点。
基本信息
| 反例 | sha256(secret + message),可被长度扩展利用 |
|---|---|
| 正例 | HMAC-SHA256(RFC 2104 构造) |
| 天然替代 | SHA-3 与 BLAKE2 的海绵结构本身抗长度扩展 |
| 比较方式 | 使用 hmac.compare_digest 做常量时间校验 |
| 密钥长度 | 任意长度均可,过短会削弱安全性 |
| 典型应用 | Webhook 签名、接口鉴权、会话令牌 |
典型使用场景
- 接收 Webhook:按平台文档构造待签串并用 HMAC 校验来源
- 接口鉴权:用共享密钥对请求参数签名,防止中间篡改
- 一次性令牌:对数据附加 HMAC 作为完整性标记
- 代码迁移:把历史代码里拼接密钥的写法替换为 HMAC
- 对接排查:确认待签串的字段顺序与编码与文档完全一致
正反例对照(Python)
正反例对照(Python)
import hashlib, hmac
# 反例:不要这样校验来源,存在长度扩展风险
bad = hashlib.sha256(secret + msg).hexdigest()
# 正例:HMAC 双层结构,长度扩展不适用
good = hmac.new(secret, msg, hashlib.sha256).hexdigest()
# 比较必须用常量时间函数,避免通过响应耗时逐字节猜签名
ok = hmac.compare_digest(good, received)构造对照
| 构造 | 抗长度扩展 | 评价 |
|---|---|---|
| 哈希(密钥 与 消息 拼接) | 否 | 不要用于来源校验 |
| 哈希(消息 与 密钥 拼接) | 否 | 同样不规范 |
| HMAC | 是 | 标准做法,生态支持好 |
| 带密钥的 SHA-3 | 是 | 海绵结构天然免疫 |
许多 Webhook 与开放平台使用 HMAC-SHA256 作为签名算法,对接时应严格按文档构造待签串并做常量时间比较。
比较签名的代码必须使用常量时间函数:普通字符串相等比较会提前返回,攻击者可以据此逐字节试探出正确签名。
密钥管理同样关键:Webhook 密钥应当支持轮换,并留出同时接受新旧两个密钥的过渡期,避免换密钥时业务中断。