完整课程入口:996 全套课程体系 | Lua 学习路径 | 幂尔框架 mirs.cn
重放(Replay Attack)不破解你的加密,它只是把"你合法发出的数据"原样再发一遍。典型场景:客户端领取奖励的请求被截获,攻击者原封不动重发一百次——请求签名、加密全部合法,服务器照单全收。只要"同样的数据可以无限次有效",你的协议就有重放漏洞。
防重放的核心是让每条请求"只有效一次"。两个要素组合:
时间戳:请求带客户端时间,服务器拒绝超出窗口(如 ±60 秒)的请求,过期报文直接作废。
随机数 nonce:每条请求带一个一次性随机串,服务器记录最近 N 分钟内出现过的 nonce(内存哈希表 + 定期清理),重复即拒收。时间戳挡"旧包重放",nonce 挡"窗口内的连发重放",缺一不可。
-- 客户端组包
local nonce = randomHex(16)
local sign = hmac(key, seq .. ":" .. ts .. ":" .. nonce .. ":" .. body)
-- 服务器验包
if math.abs(now - ts) > 60 then return reject("过期") end
if seenNonces[nonce] then return reject("重放") end
seenNonces[nonce] = true
if not verifySign(key, seq .. ":" .. ts .. ":" .. nonce .. ":" .. body) then return reject("伪造") end
签名(HMAC-SHA256)保证两件事:包体未被篡改、发送方持有密钥。覆盖范围务必包含全部关键参数——只签消息类型不签参数,等于没签;只签 body 不签序号,攻击者可以调换包序。会话密钥在登录握手时由服务器下发并定期轮换,不要与加密密钥复用同一把。
游戏协议还要加一层序号校验:客户端每条消息自增 seq,服务器记录已处理的最大 seq,跳回的旧序号直接拒收。序号是天然的重放屏障,实现成本几乎为零。
网络层的锁都可能被绕过(客户端毕竟是别人的机器),所以关键业务必须在服务端做幂等设计:每条领取类请求携带客户端生成的唯一业务单号,服务器按单号去重——同一单号第二次到达,返回第一次的结果而不是重复发货。把"防重放"下推到业务语义层,才是真正兜底。
三道措施(时间戳+nonce、HMAC 签名+序号、业务单号幂等)加起来不到两百行代码,却能挡掉绝大多数协议层薅羊毛手法,是新服上线前的标准自查项。
md5str(str)
MD5加密,包体签名的最基础一环,注意把字段按约定顺序拼接后再算
| 参数 | 类型 | 说明 |
|---|---|---|
| str | string | “需要加密的文本 |
getrandomtext (path,str)
从文件中随机获取一行字符串,每次会话取一条不同的盐,落盘一份便于排查
| 参数 | 类型 | 说明 |
|---|---|---|
| path | string | 文件路径(必填参数) |
| str | string | 指定行(0~1000)传入-1随机取某一行的字符串(必填参数) |
handlerequest(actor, msgid, param1, param2, param3, sMsg)
监听消息,收包后第一件事是校验签名,再进业务
| 参数 | 类型 | 说明 |
|---|---|---|
| actor | object | 玩家对象(必填参数) |
| msgid | integer | 消息ID(必填参数) |
| param1 | integer | 参数1(必填参数) |
| param2 | integer | 参数2(必填参数) |
| param3 | integer | 参数3(必填参数) |
| sMsg | string | 消息内容(必填参数) |
logincheckent(userID)
检测登陆信息[反外挂],登录链路上的既有校验,自定义协议不要绕过
| 参数 | 类型 | 说明 |
|---|---|---|
| userID | string | 玩家唯一ID(必填参数) |
sendloginmsg(userID)
发送登陆信息[反外挂],把签名所需信息随登录一起下发
| 参数 | 类型 | 说明 |
|---|---|---|
| userID | string | 玩家唯一ID(必填参数) |
parsetext(text, object)
解析文本,批量的白名单与版本号文本用引擎的解析口处理
| 参数 | 类型 | 说明 |
|---|---|---|
| text | string | 文本内容(必填参数) |
| object | object | 玩家对象(1-50)(必填参数) |