必须用 hmac.new() 生成 hmac,禁用 key+msg 拼接;密钥和消息均需 bytes 类型;验证必须用 hmac.compare_digest() 防时序攻击;digestmod 应传哈希构造函数如 hashlib.sha256。

python 里 hmac 模块怎么用才不踩坑
直接说结论:别自己拼接密钥和消息再哈希,必须用 hmac.new();否则结果不可靠,且容易被时序攻击绕过。
常见错误是写成 hashlib.sha256(key + message).digest() —— 这根本不是 HMAC,既不符合 RFC 2104,也不防长度扩展攻击,连签名验证都对不上。
-
hmac.new()内部做了密钥标准化(key长度不足时补零、超长时先哈希)、两次哈希(inner/outer pad),手动模拟极易出错 - 密钥必须是
bytes,传str会报TypeError: key: expected bytes or bytearray, but got 'str' - 消息也必须是
bytes,hmac.new(key, b'msg', digestmod=hashlib.sha256)是安全的写法;用.encode()要明确指定编码,比如msg.encode('utf-8')
为什么 hmac.compare_digest() 不是可选,而是必须用
普通 == 比较字符串或字节串是“短路”的:从左到右逐字节比,第一个不同就返回 False。攻击者通过测量响应时间,能反推出签名前缀,逐步恢复完整 MAC。
hmac.compare_digest() 强制比较所有字节,时间恒定,是唯一推荐的验证方式。
立即学习“Python免费学习笔记(深入)”;
- 只接受两个
bytes或两个bytearray,类型不一致会直接抛TypeError - 哪怕长度不同,它也会把短的那个补零再比完全部长度,避免泄露长度信息
- 别用在非密钥场景(比如普通用户密码比对),它没做慢哈希,仅防时序侧信道
hmac.new() 的 digestmod 参数到底该怎么填
这个参数决定底层哈希算法,但填法有讲究:可以传哈希构造函数(如 hashlib.sha256),也可以传算法名字符串(如 'sha256'),但行为不完全等价。
- 传函数(
hashlib.sha256)最稳妥,兼容所有 Python 版本,且明确绑定哈希逻辑 - 传字符串(
'sha256')在 Python 3.9+ 才支持,旧版本会报ValueError: Unsupported hash name - 别传
hashlib.sha256()(带括号)——那是个实例,不是构造器,会直接报TypeError: 'SHA256' Object is not callable - 常见可用值:
hashlib.md5、hashlib.sha1、hashlib.sha256、hashlib.sha512;sha1和md5仅限兼容旧系统,新项目禁用
生产环境 HMAC 签名验签的典型流程和陷阱
签名本身不难,难的是上下文处理:编码、截断、传输格式、密钥管理。漏掉任意一环,服务端和客户端就对不上。
- 消息体必须用确定编码(通常是 UTF-8),且不能含 bom;空格、换行、字段顺序都要严格一致
- 签名后建议用
base64.b64encode()转成文本传输,别直接发 raw bytes,http header 或 URL 里会乱码 - 密钥绝不能硬编码,也不能用环境变量明文存——应走密钥管理服务(KMS)或至少用
os.urandom()生成并安全存储 - 验证失败时,不要返回具体原因(比如 “MAC mismatch” 或 “timestamp expired”),统一返回 401 或 403,避免信息泄露
实际用的时候,最常卡住的不是算法本身,而是消息预处理不一致——比如前端 json.stringify() 和后端 json.dumps() 的键序、空格、NaN 处理差异,导致 HMAC 完全对不上。这点没法靠看文档发现,只能靠日志打原始 bytes 对齐。