对接一个没有文档的第三方渠道,最常卡住的地方不是接口参数,是那个 sign。对方前端做了个加密,你调接口它不认,官方文档要么没有、要么写的不是这一版。
我先说结论:能拿到官方签名规则,就别逆向。微信支付 v2 的 MD5 拼接、v3 的 WECHATPAY2-SHA256-RSA2048 五段签名串、支付宝 RSA2 的待签名字符串,文档都写得明明白白,老老实实按文档走比什么都快。需要逆向的,通常是你拿不到文档的灰色渠道、聚合支付网关,或者文档和实际实现对不上的情况。下面这套方法我在支付网关对接里反复用,写下来省得下次再踩一遍。
先判断要不要逆向,省掉一半时间
很多渠道所谓"没有签名文档",其实是签名的 key 和规则都给了,只是你没看懂。签名串就那几种拼法,先对着抓包看:
sign长度 32 位,大概率 MD5;40 位是 SHA1;64 位是 SHA256。- 同一接口发两次,看哪些字段变了(
timestamp、nonce、sign),哪些不变。 - 请求里有没有
_sign、signature、Authorization这类别名。
90% 的情况就是:参数按 key 字典序排序 → 拼成 k=v&k=v → 尾部拼固定盐值 → MD5 转大写。这个规则先手写出来验证,别一上来就上补环境,那是最后的解法。
定位签名生成的入口
实在要逆向,路径是固定的:抓包 → 全局搜索 → 断点 → 抠函数。
先在 Network 里找到带 sign 的请求,然后全局搜索 sign=、signature、md5(、encrypt( 这些关键词。位置通常在:
- 公共请求封装(axios/fetch 拦截器)
- 工具类
utils.js/crypto.js - 压缩混淆后的
vendor.js/chunk.js
搜不到参数名是常态,因为被混淆了。这时候别硬搜,用 Hook:在 Object.defineProperty 或请求对象的赋值处下断点,让它算出来的那一刻自己断下来,顺着调用栈往上找。这一步能省大量翻源码的时间。
抠出拼接规则,用控制变量法
核心不是读代码,是改一个参数看 sign 变不变:
- 只改一个参数值,sign 变没变。
- 去掉
timestamp,sign 变没变(验证时间戳是否参与)。 - 调整参数顺序,sign 变没变(验证是否字典序)。
- 改 header 里的
deviceId、clientType,sign 变没变(很多渠道把 header 字段也塞进签名,这个最容易被忽略)。
我遇到过一个案例,签名串里带上了 User-Agent 的前 20 个字符,抓包分析了一下午,最后是在控制台逐步打印才发现的。所以别只看 body,header 也要纳入控制变量范围。
6 个高频坑,按出现频率排
- 参数顺序:绝大多数渠道要求字典序,乱序必错。但也有少数渠道按固定字符串拼,不排序,别想当然。
- 大小写:MD5 结果是转大写还是小写,必须严格匹配。转错一次就够你怀疑人生。
- 空值:
key=(有 key 空值)和没有这个 key,结果是两回事。微信文档明确写了"值为空不参与签名",但第三方不一定。 - 编码:中文、特殊字符要 UTF-8 或 URLencode,且签名原串用的是参数原值,不是 URLencode 后的值。微信文档特别强调过这一点。
- 时间戳精度:秒级还是毫秒级必须一致,自己生成的和签名的必须同一个。
- 隐藏字段:header 里的设备指纹、随机串、
appKey,可能都参与了签名,抓包没抓到就永远对不上。
什么时候果断放弃逆向
有几个信号出现,就别硬抠了,成本会失控:
- 加密逻辑在 WebAssembly 或 JSVMP 虚拟机里,补环境的工作量远超收益。
- 服务端做了环境检测(
navigator、performance、getBoundingClientRect返回值校验),你在 Node 里补的环境经常对不上。 - 第一次调通、过几天又失效——大概率是动态密钥或者有效期机制,得考虑 JSRPC 挂浏览器常驻。
补环境我最后才用,而且只在确认签名规则已还原、只差运行环境时才做。方法就是下载那个生成 sign 的 JS 文件,在 Node 里用 jsdom 把 window、document、navigator 补全,缺什么补什么。常见判断:签名长度对但服务端拒收,通常是环境指纹不匹配;长度都不对,是算法或拼接错了。
一条边界
逆向拿到的签名做自动调用,只该用于你自己的账号、你对接的渠道。别拿这套去爬别人平台的接口做批量采集,别碰有明确条款禁止自动化的站——那不只是技术问题,是合规红线。本文案例只讲方法论,具体渠道规则需自行验证。
对接支付接口,sign 校验失败时先别怀疑人生。按"抓包 → 搜关键词 → 断点 → 控制变量改一个参数"的顺序走,八成是上面 6 个坑里的某一个,只是你没在正确的位置下断点。