hmac.new() 的 key 和 message 必须为 bytes 类型,字符串需显式调用 .encode("utf-8") 转换;校验时须确保原文字节完全一致(含参数顺序、json separators、url 编码等),并使用 hmac.compare_digest() 安全比对。

Python 中 hmac.new() 的 key 和 message 参数必须是 bytes
直接传入字符串会抛出 TypeError: key: expected bytes, but got str。这是最常踩的坑,尤其在读取配置文件或环境变量拿到 secret_key 时,默认是字符串。
解决方法很简单:统一用 .encode() 转换,推荐显式指定编码(如 UTF-8),避免隐式编码差异:
import hmac
import hashlib
<p>secret_key = "my_secret_123" # 来自配置
message = "timestamp=1715823400&data={\"id\":123}" # 待签名原文</p><h1>✅ 正确:全部转为 bytes</h1><p>sig = hmac.new(
secret_key.encode("utf-8"),
message.encode("utf-8"),
hashlib.sha256
).digest()</p><h1>❌ 错误:混合类型或未 encode</h1><h1>hmac.new(secret_key, message, hashlib.sha256) # 报错</h1><p></p>
校验端必须还原出完全一致的待签原文(order、encoding、空格)
签名本身不包含结构信息,hmac 只对输入字节流做运算。如果客户端按 json.dumps(data, separators=(',', ':')) 拼接,而服务端用 json.dumps(data)(默认带空格),二者字节不同,签名必然失败。
常见需对齐的点:
- 参数顺序:GET 查询参数需按字母序排序后拼接(如
data=...×tamp=...→data=...×tamp=...) - JSON 序列化:统一用
separators=(',', ':')去除空格 - URL 编码:若参数含特殊字符(如
+、/),双方都需对 key/value 单独urllib.parse.quote(),且保持 safe 参数一致(例如safe='') - 时间戳精度:确认是否用秒级
int(time.time())还是毫秒级,单位不一致会导致原文不同
使用 hmac.compare_digest() 防侧信道攻击
不要用 == 直接比对签名结果。因为字符串比较在遇到第一个不匹配字节时就提前返回,攻击者可通过响应时间差异推测签名内容(timing attack)。
必须用恒定时间比较函数:
# ✅ 安全比对(bytes 之间)
expected_sig = base64.b64encode(sig).decode("ascii") # 假设客户端传 base64
received_sig = request.headers.get("X-Signature")
<p>if received_sig and hmac.compare_digest(
expected_sig.encode("ascii"),
received_sig.encode("ascii")
):</p><h1>校验通过</h1><pre class="brush:php;toolbar:false;"><code>pass</code>❌ 危险:可能暴露签名长度或前缀
if expected_sig == received_sig:
注意:hmac.compare_digest() 要求两个参数都是 bytes,所以需先 encode;如果原始签名是 hex 字符串,用 bytes.fromhex() 转,别用 binascii.unhexlify()(它对非法字符更敏感)。
调试时打印 raw signature 而非 base64/hex 后的结果
当校验失败却看不出原因时,最有效的方式是:在客户端和服务端都打印 hmac.new(...).digest() 的十六进制表示(即 .digest().hex()),而不是 base64 编码后的值——因为 base64 是可逆编码,但一旦两边编码方式不同(比如 padding 处理差异),base64 字符串看起来就不一样,反而掩盖了真实字节是否一致。
快速验证步骤:
- 客户端打印
hmac.new(key, msg, hashlib.sha256).digest().hex() - 服务端用同样 key + 同样 msg 计算并打印 hex 结果
- 逐字符比对两段 hex 字符串 —— 如果一致,说明签名逻辑无误;不一致,则问题一定出在 msg 构造环节
这个 hex 字符串就是真相,其他任何中间格式(base64、大写 hex、带冒号的 hex)都只是包装,容易引入额外误差。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











