什么是重放攻击?接口如何用时间戳、Nonce 和签名防重放?
简化版
重放攻击是攻击者截获一次合法请求后,不修改内容直接再次发送,让服务端重复执行操作。防御通常使用 HTTPS、时间戳、Nonce、请求签名和幂等控制:服务端校验请求是否在有效时间窗内、Nonce 是否用过、签名是否匹配。
详细版
重放攻击的关键不是伪造内容,而是复用旧的合法内容。比如抓到一次扣款请求,如果服务端只校验签名合法,却不校验时间和唯一性,攻击者可能重复发送导致多次扣款。
常见方案是把方法、路径、参数摘要、时间戳、Nonce 一起参与签名。服务端先校验时间窗口,例如 5 分钟内有效,再检查 Nonce 是否已使用,最后验证签名。关键业务还要有幂等号,避免网络重试和恶意重放造成重复执行。
完整版教学
一、重放攻击的本质
重放攻击利用的是“旧请求曾经合法”。攻击者不一定知道密钥,也不一定能改参数,只要能拿到一份完整请求并再次发送,就可能让服务端重复执行。
合法请求: pay order=100 amount=99 sign=abc
攻击者复制
再次发送: pay order=100 amount=99 sign=abc
如果服务端只看签名正确,就会误以为这是一次新的合法请求。
记忆钩子:重放攻击不是“改请求”,而是“旧请求再次被当成新请求”。
二、为什么 HTTPS 还不够
HTTPS 能防止大多数网络窃听和篡改,但不能覆盖所有重放来源。比如客户端日志泄露、代理层记录、内部服务调用被复制、恶意客户端自己重复发送。
TLS 防链路窃听
时间戳/Nonce 防旧请求重复使用
幂等号防业务重复执行
所以防重放通常是应用层协议设计的一部分,尤其是开放 API、支付、下单、回调接口。
三、时间戳窗口怎么用
请求里带时间戳,服务端只接受当前时间附近的请求。例如允许误差 300 秒,超过就拒绝。
server_now = 12:00:00
allowed window = 11:55:00 ~ 12:05:00
request_ts = 11:40:00 -> reject
时间窗口能缩短旧请求可利用时间,但不能阻止窗口内重复发送,所以还需要 Nonce。
四、Nonce 为什么必须记录
Nonce 是一次性随机数。服务端要记录某个客户端在时间窗口内用过的 Nonce,再次出现就拒绝。
key = client_id + nonce
Redis SETNX key 1 EX 300
成功: 第一次使用
失败: 重放请求
如果只要求客户端带 Nonce 但服务端不记录,它就没有防重放作用。记录时间通常与时间戳窗口一致。
五、签名应该覆盖哪些内容
签名要覆盖请求方法、路径、查询参数、body 摘要、时间戳、Nonce 和客户端标识。否则攻击者可能复用签名到另一个路径或替换未签名字段。
string_to_sign =
METHOD + "\n" +
PATH + "\n" +
SHA256(body) + "\n" +
timestamp + "\n" +
nonce
签名算法常用 HMAC-SHA256。服务端使用共享密钥重新计算并做恒定时间比较,避免时序侧信道。
六、幂等和防重放的区别
防重放关注“同一请求不能被旧包重复利用”,幂等关注“业务重复调用不会重复产生副作用”。两者经常一起用,但不是同一件事。
| 机制 | 解决问题 | 示例 |
|---|---|---|
| 时间戳 | 旧请求过期 | 5 分钟窗口 |
| Nonce | 同窗口重复请求 | SETNX 去重 |
| 签名 | 请求未被篡改 | HMAC |
| 幂等号 | 业务重复执行 | order_id/request_id |
支付、创建订单、发券这类接口,只有防重放没有幂等仍然不够稳。
七、开放 API 的落地流程
开放 API 通常给每个调用方分配 app_id 和 secret。客户端按约定构造待签名字符串,服务端根据 app_id 找到密钥重新计算签名。签名失败、时间过期、Nonce 重复都要拒绝。
client sends: app_id, timestamp, nonce, signature
server loads secret by app_id
server verifies time -> nonce -> signature
校验顺序也有讲究:先做便宜的时间窗口和格式校验,再做存储查询和签名计算,避免攻击者用垃圾请求耗尽资源。
八、回调接口的特殊点
支付回调、消息回调、物流回调经常会被服务方重试。你的服务不能把所有重复回调都当攻击,也不能重复执行业务动作。正确做法是验证来源签名,再用业务单号做幂等。
same callback + same order_id
first time: update paid
second time: return success without duplicate update
这类场景要把“合法重试”和“恶意重放”同时考虑,日志里也要能区分。
九、常见误区与追问
- 误区:签名正确就一定不是重放。 旧请求签名也正确,必须校验时间戳和 Nonce。
- 误区:Nonce 不用存储。 不记录已用 Nonce,就无法判断重复。
- 误区:HTTPS 后不需要应用层防重放。 开放 API、回调和内部复制请求仍可能需要。
- 追问:时间窗口设多大? 要平衡时钟误差和攻击窗口,常见是 1 到 5 分钟。
- 追问:服务端时间不一致怎么办? 使用 NTP,对客户端误差给小窗口,并监控异常。
- 追问:签名为什么要包含 path 和 body? 防止签名被挪用到其他接口或参数被替换。
- 追问:Redis 记录 Nonce 挂了怎么办? 要按业务决定 fail-open 或 fail-close,支付类通常更保守。
十、加强记忆
重放攻击要按“旧请求再次有效”来理解。防线是四件套:HTTPS 防链路窃听,时间戳限制旧请求窗口,Nonce 证明一次性,签名绑定请求内容,幂等号兜住业务重复执行。面试回答时把这几层分清,就能从协议安全讲到工程落地。