hmac签名本质是用密钥和请求内容生成不可伪造的数字指纹,通过字节级严格一致的拼接、显式utf-8编码、服务端时效性校验与头信息验证,实现防篡改、防冒充、防重放。

接口权限签名本质是用密钥和请求内容共同生成一个不可伪造的“数字指纹”,HMAC 就是最常用、最可靠的实现方式。它不传输密钥,只比对指纹,既防篡改,也防冒充。
密钥必须用 []byte,不能靠 string 隐式转换
Go 的 hmac.New 明确要求第二个参数是 []byte。传 string 看似能过编译,但中间若经过 fmt.Sprintf、string([]byte) 或配置文件读取等环节,极易混入 BOM、UTF-8 控制符、前后空格或换行符——这些肉眼不可见的字符会让密钥实际字节与服务端不一致,验签必然失败。
- ✅ 正确做法:直接从环境变量读取后强转,如
[]byte(os.Getenv("API_SECRET")),全程无中间字符串操作 - ⚠️ 验证手段:测试时打印
len(key)和fmt.Printf("%q", key),确认输出中没有\u{feff}、\n、\r或末尾空格
签名原文拼接必须字节级完全一致
服务端和客户端哪怕只差一个空格、一个大小写、一个编码方式,hmac.Equal 就返回 false。关键字段包括:
-
method:全大写,如
"GET",不是"get" -
path:不含 query,如
"/api/v1/user",不是"/api/v1/user?x=1" -
timestamp:秒级整数字符串(非毫秒),如
"1715234000",末尾不能有空格 - nonce:客户端生成的随机字符串,建议 16 位以上,每次请求唯一
-
body_hash:对原始 body 字节做
sha256.Sum256,再 hex 编码为小写(如"a1b2c3...");JSON body 字段顺序不同会导致哈希不同,建议规范序列化
query 参数不能直接用 req.URL.RawQuery(顺序随机),应解析后按 key 字典序排序,每个 value 用 url.QueryEscape(不是 PathEscape),空格变 %20 而非 +。
服务端验签要同步检查时效性与头信息
HMAC 本身只保完整性,防不了重放攻击。服务端必须额外验证:
- 时间戳是否在允许偏差范围内(如
clock_skew = 300秒),需用 GMT 格式的Date头或自定义时间戳头 - 是否包含指定的签名头(如
X-Signature、X-Timestamp、X-Nonce),缺失即拒收 - 若开启
validate_request_body,还需校验Digest: SHA-256=xxx头,确保 body 未被中间设备修改
Python 客户端可复用 Requests 的 AuthBase
用 requests.auth.AuthBase 封装签名逻辑,让调用像写普通请求一样简洁:
- 继承
AuthBase,在__call__中组装 method、url、sorted params、body hash、timestamp - 签名基串格式统一为:
"{METHOD}&{PATH}&{QUERY_STRING}&{TIMESTAMP}&{NONCE}&{BODY_HASH}" - 密钥用
.encode("utf-8")显式转字节,哈希用hmac.new(key, msg, hashlib.sha256) - 最终签名 base64 编码后放入
Authorization或自定义头,如X-Signature: base64(...)











