API 请求签名设计指南(免费版)
一、它解决什么问题
HTTPS 保证传输过程不被窃听,但仍有三类风险:
1. 篡改:请求参数(金额、收款账号)在到达前被改动;
2. 伪造:攻击者拿到一个合法请求格式后,伪造请求调用接口;
3. 重放:截获一个合法请求,稍后原样重发(如重复下单/重复扣款)。
HMAC 签名:发送方与接收方共享一个密钥,发送方对"请求关键要素"计算 HMAC,
接收方用同一密钥重算并比对——不一致即拒绝。密钥不在网络传输,攻击者无法伪造签名。
二、签名串拼接规范
把请求的关键要素按固定顺序、固定分隔符拼成规范串(canonical string):
HTTP方法\n
请求路径\n
时间戳\n
随机串nonce\n
请求体SHA256摘要
| 要素 | 作用 | 要点 |
|---|---|---|
| HTTP方法 | 绑定操作类型 | 统一大写(POST/GET) |
| 请求路径 | 绑定目标资源 | 只用 path,不含域名;query 参数需排序后纳入 |
| 时间戳 timestamp | 防重放 | Unix 秒;接收方校验新鲜度 |
| 随机串 nonce | 防同秒重复 | 每次唯一,可配合服务端去重 |
| 请求体摘要 | 防 body 篡改 | 对 body 算 SHA256,避免长 body 直接签名 |
关键原则:发送方与接收方的拼接规则必须逐字节一致,否则全部验签失败。
三、防重放机制
- 接收方校验
|服务器时间 - 请求时间戳| ≤ 容差(常取 300 秒),超出直接拒绝; - 容差窗口内,用 nonce 去重:已处理过的 nonce 再次出现即拒绝;
- 时间戳过期(旧请求)与超前(伪造时钟)都应拒绝。
四、Webhook 签名
向客户系统推送事件时,常用 Stripe 风格格式,签名内容更简单:
签名串 = "{timestamp}.{原始请求体}"
签名 = HMAC-SHA256(secret, 签名串)
- 请求头示例:
X-Signature: t=时间戳, v1=签名值; - 客户用 body 原始字节重算(JSON 重新序列化会改变字节导致验签失败);
- 支持多版本密钥时用
v1=、v2=共存,便于平滑轮换。
五、密钥管理
- 每个对接方使用独立密钥,泄露一个不影响其他方;
- 密钥只在服务端保存,不写进前端、日志、错误信息;
- 支持密钥轮换:新旧密钥设重叠期,头中可带
keyId标识所用密钥; - 验签一律使用恒定时间比较(compare_digest),防止时序攻击。
六、落地检查清单
- [ ] 签名串拼接顺序、分隔符、编码(UTF-8)双方一致并写入文档
- [ ] query 参数有确定排序规则,body 用原始字节算摘要
- [ ] 校验时间戳容差 + nonce 去重,覆盖过期与超前
- [ ] hex/base64 编码统一;签名比较用恒定时间函数
- [ ] 验签失败、时间戳异常、nonce 重复分别记录日志并告警
- [ ] Webhook 客户文档强调"用原始 body 验签"
- [ ] 每对接方独立密钥,具备轮换与吊销流程
- [ ] 密钥不进代码仓库、日志、报错
七、硬性规则
- 签名覆盖的要素必须包含任何"可被利用的参数"(金额、账号、操作对象)。
- 必须同时有时间戳新鲜度校验与 nonce 去重,缺一不可。
- 密钥绝不在网络传输或写入前端;验签比较必须恒定时间。
- 本指南为设计方法;需要可直接调用的签名/验签计算,请使用专业版。