2. 使用HMAC实现消息认证码
HMAC 的核心结构与验证流程
HMAC(Hash-based Message Authentication Code)不是简单地把密钥拼在消息后面再哈希,而是通过内外两层哈希结构把密钥混入摘要计算,从而抵抗长度扩展攻击。其标准形式为:
HMAC(K, M) = H( (K ⊕ opad) || H( (K ⊕ ipad) || M ) )
其中 ipad 为字节 0x36 重复到哈希块大小,opad 为字节 0x5C 重复到哈希块大小。以 SHA-256 为例,块大小为 64 字节。密钥若短于块大小则右侧补零,若长于块大小则先哈希压缩。
使用 Python 标准库实现 HMAC
Python 的 hmac 模块配合 hashlib 可直接调用 HMAC 接口。以下示例使用 SHA-256 作为底层哈希,密钥为 32 字节随机值,消息为 UTF-8 编码的文本。
import hmac
import hashlib
import os
# 1. 生成 32 字节共享密钥(实际场景中通过安全信道分发)
key = os.urandom(32)
print("密钥(hex):", key.hex())
# 2. 待认证消息
message = "转账: 从A账户到B账户, 金额=1000, 时间=2025-01-15T10:30:00Z"
msg_bytes = message.encode("utf-8")
# 3. 生成 HMAC-SHA256 认证码
mac = hmac.new(key, msg_bytes, hashlib.sha256)
tag = mac.digest() # 32 字节二进制
tag_hex = mac.hexdigest() # 64 字符十六进制
print("HMAC(hex):", tag_hex)
# 4. 接收方验证(模拟)
received_msg = msg_bytes
received_tag = tag
# 重新计算并比较
expected = hmac.new(key, received_msg, hashlib.sha256).digest()
is_valid = hmac.compare_digest(expected, received_tag)
print("验证结果:", is_valid) # True
# 5. 篡改消息后验证
tampered = "转账: 从A账户到B账户, 金额=9999, 时间=2025-01-15T10:30:00Z".encode("utf-8")
expected2 = hmac.new(key, tampered, hashlib.sha256).digest()
print("篡改后验证:", hmac.compare_digest(expected2, received_tag)) # False
关键点:验证时必须使用
hmac.compare_digest() 而非 ==。普通字符串比较会在第一个不同字节处提前返回,攻击者可通过响应时间差异逐字节猜测认证码,这就是时序攻击(timing attack)。compare_digest 保证恒定时间比较。
参数与行为对照
| 参数/行为 | 说明 | 示例值 |
|---|---|---|
| 底层哈希算法 | 决定输出长度与安全强度 | SHA-256 → 32 字节输出 |
| 密钥长度 | 建议 ≥ 哈希输出长度;短于块大小自动补零 | 32 字节(256 位) |
| 块大小 | 由哈希算法决定,SHA-256 为 64 字节 | 64 字节 |
| ipad / opad | 固定填充字节,用于内外层密钥混合 | 0x36 / 0x5C |
| 消息编码 | 发送方与接收方必须一致 | UTF-8 |
| 比较方式 | 必须恒定时间 | compare_digest |
验证清单
- 发送方与接收方使用相同的密钥和相同的哈希算法。
- 消息在传输前完成编码(如 UTF-8),接收方按相同方式解码/编码后再计算。
- 认证码与消息一起传输,但密钥绝不随消息传输。
- 比较认证码时使用恒定时间函数,避免时序侧信道。
- 若验证失败,需区分是消息被篡改还是密钥不匹配——两者产生相同的失败结果,需通过带外方式排查。