PHP防重放必须签名而非仅校验timestamp和nonce:攻击者可重放合法请求,需用hash_hmac('sha256', $data_string, $secret_key)签名,覆盖timestamp、nonce、业务参数及密钥,并严格保证参数字典序排序、urlencode、Redis SETNX原子存nonce、时间窗口±5分钟容错。

PHP中时间戳+随机数校验为何不能单独防重放
单纯在请求里传 timestamp 和 nonce(随机数),服务端只做“是否已用过该 nonce”或“timestamp 是否超5分钟”,是无效的。攻击者截获一次合法请求后,可立刻重放——只要时间仍在窗口内、nonce 未被服务端全局去重(比如没存 Redis 或 DB),校验就形同虚设。
必须配合签名(sign)且签名覆盖时间戳与随机数
签名才是防重放的核心防线:它把 timestamp、nonce、业务参数、密钥一起哈希,确保任何字段篡改或重放都会导致 sign 不匹配。常见错误是签名漏掉 timestamp 或 nonce,或排序不一致导致客户端和服务端算出不同结果。
- 推荐用
hash_hmac('sha256', $data_string, $secret_key),比md5()或裸sha1()更安全 -
$data_string必须固定格式:所有参数按 key 字典序排序,拼成k1=v1&k2=v2×tamp=1717023456&nonce=abc123,且 value 要urlencode() - 服务端收到请求后,先校验
timestamp是否在 ±5 分钟内,再查nonce是否已存在(建议用 RedisSETNX nonce 1 EX 300),最后才验签
Redis 存 nonce 的几个关键细节
用 Redis 记录已用 nonce 是最常用做法,但容易忽略过期策略和原子性问题。
- 不能用
SET nonce 1+ 单独EXPIRE:中间可能被并发请求绕过。必须用SET nonce 1 EX 300 NX(NX保证仅当 key 不存在时才设) - key 过期时间(
EX 300)应略大于时间戳允许偏差(如 5 分钟),避免因服务器时间差导致误判 - 如果请求量极大,可考虑用
HASH分桶(如按nonce前两位分 100 个 key),避免单 key 热点
客户端时间不准怎么办?服务端要容忍一定偏差
手机 App 或浏览器本地时间可能快慢几分钟,硬性要求客户端和服务端时间完全同步不现实。服务端校验 timestamp 时,必须留出缓冲窗口。
立即学习“PHP免费学习笔记(深入)”;
- 建议检查
abs($server_time - $request_timestamp) (5 分钟),而不是只判断$request_timestamp > $server_time - 300 - 不要依赖客户端传的时区或格式,统一按 Unix timestamp(整数秒)处理,拒绝带毫秒、带时区或非数字的
timestamp - 如果业务对时效性极高(如支付确认),可额外增加
seq(单调递增序列号)并维护用户级最新seq,但会提高服务端状态管理成本
真正难的不是生成 sign 或存 nonce,而是所有环节——参数排序规则、urlencode 行为、Redis 命令原子性、时间窗口定义——必须客户端和服务端严丝合缝。一个空格、一个未编码的特殊字符、一次漏掉 NX,都可能导致防线失效。











