接口签名验证机制设计方法详解(详解防篡改与防重放攻击的方案)

防篡改靠 HMAC 签名保证请求完整性,防重放靠时间戳 + 一次性 Nonce 保证新鲜与唯一。三者组合:客户端用共享密钥对”方法 + 路径 + 参数 + 正文 + 时间戳 + Nonce”拼接串做 HMAC-SHA256,服务端按相同规则重算并比对,同时校验时间窗口与 Nonce 去重。下文给出规范拼接方式、可运行代码与落地清单。

一、为什么 API Key 不够

API Key 或 OAuth Token 只能认证”谁在调用”,挡不住两类攻击:攻击者截获合法请求后改参数(篡改金额),或原样重发(重复下单扣款)。HMAC 签名让任何字段改动都导致验签失败,时间戳与 Nonce 再补上”新鲜 + 唯一”。

签名验证服务端清单要求:时间戳在小窗口内(如 ±300 秒)、Nonce 未用过、按客户端相同规则重建待签串、用 keyId 选密钥并做恒定时间比较。

二、待签串的规范拼接

不同客户端拼接顺序不一致会让签名忽对忽错,必须定义规范化(canonical)规则。一份 API 文档把待签串定为:HTTP 方法 + 路径含查询 + 时间戳(毫秒)+ 正文。更稳妥的做法是对查询参数与头部按字典序排序,并包含正文 SHA-256 摘要。

import hashlib, hmac, time, uuid, json

def build_canonical(method, path, query, body_bytes, ts, nonce):
    qs = "&".join(f"{k}={query[k]}" for k in sorted(query))
    body_hash = hashlib.sha256(body_bytes).hexdigest
    return "\n".join([
        method.upper, path, qs,
        f"x-timestamp:{ts}", f"x-nonce:{nonce}",
        body_hash
    ])

def sign(secret, canonical):
    return hmac.new(secret.encode, canonical.encode, hashlib.sha256).hexdigest

三、客户端签名与发送

  1. 发送前生成 ts = 当前毫秒时间戳 与 nonce = uuid4;
  2. 按规范拼接待签串,调用 sign 得签名;
  3. 把 X-Signature、X-Timestamp、X-Nonce 放进请求头发出。
secret = "shared-secret-with-server"
body = json.dumps({"amount": 100, "currency": "USD"}).encode
ts, nonce = str(int(time.time*1000)), str(uuid.uuid4)
canonical = build_canonical("POST", "/v1/pay", {}, body, ts, nonce)
sig = sign(secret, canonical)
# 请求头: X-Signature=sig, X-Timestamp=ts, X-Nonce=nonce

四、服务端四步校验

服务端收到请求后按序执行,任意一步失败即驳回。

步骤 校验 失败动作
1 ` now – ts
2 Nonce 未使用过 400 重放
3 重算 HMAC 与 X-Signature 一致 401 篡改
4 恒定时间比较,存 Nonce 至 TTL 通过

时间戳只挡长期重放,Nonce 挡窗口内重复,二者配合:时间戳限定 Nonce 缓存只需保留一个窗口,避免无限存储。高影响写接口再叠加幂等键 Idempotency-Key,相同键直接返回首次结果,让合法重试也安全。

func verify(r *Request, secret string) error {
    if abs(time.Now.UnixMilli-r.Ts) > 300_000 {
        return ErrExpired
    }
    if used := redis.SIsMember("nonce:"+r.ClientID, r.Nonce); used {
        return ErrReplay
    }
    redis.SAdd("nonce:"+r.ClientID, r.Nonce)
    redis.Expire("nonce:"+r.ClientID, 5*time.Minute)
    expect := hmacSHA256(secret, r.Canonical)
    if !hmac.Equal([]byte(expect), []byte(r.Sig)) { // 恒定时间
        return ErrTampered
    }
    return nil
}

五、密钥与运维

用 SHA-256 或 SHA-512,禁用 MD5/SHA-1;定期轮换密钥,旧密钥保留短宽限期避免客户端中断;密钥按 keyId 分发,前端绝不放密钥。时钟漂移用 NTP 对齐,误差大时返回服务端时间让客户端校正。TLS 仍是基础——签名防篡改重放,但传输加密防窃听,二者互补而非替代。

常见问题(FAQ)

Q1:有 TLS 还要签名吗?

要。TLS 防窃听,但截获的合法请求仍可被重放,需签名兜底。

Q2:Nonce 要存多久?

只存时间窗口长度(如 5 分钟),过期即清,勿永久存。

Q3:时间戳窗口设多大?

按风险,高危险 300 秒,低危险可放宽,配合 NTP 对齐。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
赵其鑫的头像赵其鑫管理团队

相关推荐

返回顶部