☰
JS逆向实战:百度翻译签名参数与动态令牌还原全流程
2026/10/27 21:49:45 网站建设 项目流程

JS 逆向这个方向,很多人卡在第一步——不知道从哪下手,看到一个站点密密麻麻的请求就头大。我拿「百度翻译」这个站点做练手对象已经有一段时间了,它的逆向门槛不算高,但该有的坑一个不少:签名参数、动态令牌、请求头校验、返回数据加密,几乎把常见的反爬套路都覆盖了一遍。拿它当入门到进阶的过渡案例,性价比很高。这篇内容就是把我整个处理过程完整摊开,从抓包定位、参数溯源、算法还原到最终请求复现,一步步讲清楚每一步为什么这么做、卡住了怎么绕。适合已经会一点浏览器调试、能看懂基本 JS 语法、但还没独立完整啃下一个站点的朋友。看完你至少能拿到一套可复用的分析流程,而不是只会抄某一段代码。

1. 整体思路与方案选型拆解

1.1 先想清楚要拿什么,再决定怎么打

动手之前必须先明确目标边界。翻译类站点的核心诉求通常就两类:一是拿到翻译结果文本,二是拿到词典释义、例句这类结构化数据。这两类数据走的接口往往不是同一个,参数校验强度也不一样。我一开始图省事直接冲主翻译接口,结果被签名参数卡了大半天;后来退一步先把词典查询接口跑通,摸清了它的通用请求结构,再回头攻主接口,效率高很多。

所以我的整体策略是「先易后难、先结构后算法」:先找一个校验最弱的接口把请求链路跑通,确认请求头、Cookie、公共参数这些基础设施没问题,再逐个啃签名、令牌这类硬骨头。这个顺序很重要,反过来做的话,你会在一个还没跑通的链路上反复调试算法,根本分不清是算法错了还是请求头缺了东西。

具体到百度翻译,我把它拆成四层来看:

  • 传输层:请求方法、URL 结构、公共 query 参数、请求头字段
  • 校验层:签名 sign、时间戳、动态令牌、Cookie 中的标识
  • 业务层:待翻译文本、源语言、目标语言等业务参数
  • 响应层:返回 JSON 的结构、是否有字段加密、错误码含义

这四层里,传输层和业务层是「看得见」的,抓包就能抄;校验层是「看不见」的,需要逆向;响应层是「看得见但可能看不懂」的,需要解密或字段映射。绝大多数人的时间都耗在校验层,而校验层的突破口永远在 JS 里。

1.2 为什么选浏览器调试而不是纯抓包工具

有人喜欢用抓包工具直接看请求,觉得够用了。但抓包工具只能告诉你「发了什么」,告诉不了你「为什么这么发」。签名参数是怎么算出来的、某个字段为什么这次是这个值下次变了,这些只有回到 JS 执行现场才能看清楚。

我选的是浏览器开发者工具为主、抓包工具为辅的组合。浏览器负责看调用栈、下断点、跟变量;抓包工具负责做请求重放和批量验证。这个组合的好处是:分析阶段用浏览器拿到算法逻辑,验证阶段用抓包工具快速试错,两边不打架。

提示:分析阶段不要急着写代码复现,先在浏览器里把整个调用链路走通、把每个参数的值和来源都记下来,形成一份「参数清单」,后面写代码就是照着清单填空,效率高很多。

1.3 方案选型的几个取舍

逆向方案大致有三种:纯手工还原算法、半自动(用浏览器环境跑 JS)、全自动(无头浏览器直接跑页面)。我这次的取舍是这样的:

方案优点缺点适用场景
纯手工还原运行快、资源省、可控性强工作量大、遇到混淆难啃算法清晰、参数少
半自动跑 JS省去还原工作量依赖 JS 引擎、性能一般算法复杂、混淆重
无头浏览器最省事、最接近真实资源占用大、速度慢快速验证、临时任务

百度翻译的签名算法本身不算特别复杂,属于「能手工还原」的范畴,所以我主攻纯手工还原,中间用半自动方式做交叉验证。这个选择的关键判断依据是:算法里有没有大量依赖浏览器环境对象(比如 canvas 指纹、WebGL 信息)。如果有,手工还原成本会飙升;如果没有,就值得手工啃。

2. 抓包定位与参数溯源实操

2.1 打开开发者工具,锁定目标请求

第一步永远是抓包。打开翻译页面,按 F12 切到 Network 面板,勾选 XHR/Fetch 过滤,然后在输入框里敲一段文字触发翻译。这时候会冒出若干请求,重点看两类:一类是返回翻译结果的,一类是返回词典/建议词的。

怎么快速判断哪个是目标?看响应体。返回内容里包含你输入的原文和译文的那条,就是主接口。我习惯先按响应大小排序,翻译结果通常响应体较大,容易一眼锁定。锁定之后,把这条请求的完整信息复制下来:URL、请求方法、所有 query 参数、所有请求头、请求体(如果是 POST)。

这里有个细节很多人忽略:同一个接口在不同操作下参数可能不同。比如你手动点翻译和页面自动联想触发的请求,sign 的算法可能一样但参与签名的字段集合不同。所以抓包要抓「典型场景」,最好多抓几次对比,把变化的字段和不变的字段分开。

2.2 建立参数清单,区分静态与动态

抓到请求后,我做的第一件事是列一张参数表,把每个参数标上「静态/动态」:

  • 静态参数:多次请求值不变,比如 appid、from、to、client 类型标识
  • 动态参数:每次请求都变,比如 sign、ts(时间戳)、salt、token
  • 半动态参数:跟输入内容相关,比如 query 本身、query 的长度

这张表是整个逆向的作战地图。静态参数直接抄,动态参数才是要攻的对象。我实测下来,百度翻译主接口的动态参数主要是sign和ts两个,sign依赖ts和其他字段,ts是毫秒时间戳。有些版本还会带一个token,这个 token 来自页面初始化时的一次请求,属于「会话级」参数,需要单独处理。

注意:不要假设参数名永远不变。站点改版时参数名可能微调,所以清单里除了记参数名,还要记「这个参数在请求里的位置」和「它的值长什么样」,方便改版后快速重新定位。

2.3 用 XHR 断点回溯调用栈

参数清单列好后,接下来要找到 sign 是在哪里生成的。最有效的手段是 XHR 断点:在 Sources 面板里找到 XHR/fetch Breakpoints,添加一个断点,条件填目标接口 URL 的关键片段。这样每次发请求前都会暂停,你能看到完整的调用栈。

暂停后,从调用栈自底向上看,找到最靠近业务代码的那一层。通常 sign 的生成就在发起请求前的那几帧里。我的习惯是从栈顶往下逐帧点,看每一帧的局部变量里有没有出现 sign 的值或者类似签名的字符串。一旦某一帧的变量里出现了和请求中 sign 一模一样的值,那一帧就是生成点,往上跟一层就能看到算法。

这个过程中有个技巧:在 Console 里直接搜值。把请求里的 sign 值复制出来,在 Sources 面板全局搜索(Ctrl+Shift+F),有时候能直接搜到硬编码或者拼接的地方,省去大量跟栈时间。当然混淆过的代码搜不到,那就老老实实跟栈。

2.4 定位到算法函数后的第一件事

找到算法函数后,别急着读代码。先做两件事:一是把这个函数完整复制出来,二是搞清楚它的入参和出参。入参通常是参与签名的字段集合,出参就是 sign。搞清楚这两点,你就知道「喂什么进去、期望吐什么出来」。

然后拿真实数据做一次手工验算:把抓包拿到的入参值代入,看算出来的结果和请求里的 sign 是否一致。一致,说明你找对了函数;不一致,说明还有隐藏入参或者函数被包装过。这一步是分水岭,验算通过之前不要往下走。

3. 签名算法还原与代码实现

3.1 读懂混淆代码的几个实用技巧

百度翻译的 JS 有一定混淆,但不是那种变态级别。读混淆代码我有几个固定套路:

第一,先格式化。开发者工具自带的 pretty print 能把压缩代码展开,展开后结构就清晰多了。

第二,找字符串数组。混淆代码通常把所有字符串集中到一个数组里,通过索引访问。找到这个数组和访问函数,就能把大部分「看不懂的字符串」还原成明文。

第三,盯住加密相关的关键字。搜sign、md5、sha、encrypt、btoa、charCodeAt这些词,能快速定位到核心逻辑。百度翻译的签名用到了常见的哈希运算,搜这些词命中率很高。

第四,用 Console 动态验证。读到可疑函数,直接在 Console 里调用它,传几个测试值看输出,比干读代码快得多。

3.2 签名逻辑的拆解与复现

我实测下来,百度翻译的 sign 生成逻辑大致是:把若干字段按固定顺序拼接成一个字符串,再对这个字符串做一次哈希运算,最后可能再做一次编码。拼接顺序是关键,顺序错了结果就全错。

复现的时候,我建议先用 Python 写一版,因为 Python 调试方便、字符串处理直观。核心步骤是:

import hashlib import time def build_sign(query, salt, ts, appid, secret_key): # 拼接顺序必须和 JS 中完全一致,这里仅为示意 raw = appid + query + str(salt) + str(ts) + secret_key # 哈希算法要和 JS 中一致,常见是 md5 sign = hashlib.md5(raw.encode('utf-8')).hexdigest() return sign

写完之后,拿抓包的真实数据代入,和请求里的 sign 对比。这一步必须完全一致才算通过,差一个字符都不行。如果不一致,排查方向按优先级来:拼接顺序、分隔符、大小写、编码方式(UTF-8 还是 GBK)、哈希算法(md5 还是 sha1/sha256)。

我踩过的一个坑是:JS 里对 query 做了encodeURIComponent,而我在 Python 里直接用了原文,导致中文输入时 sign 对不上。后来在拼接前统一做一次 URL 编码才通过。这类编码差异是签名对不上的高频原因,务必逐字段核对。

3.3 时间戳与令牌的处理

ts是毫秒时间戳,直接用int(time.time() * 1000)生成即可。但要注意时区问题——如果服务端校验时间偏差,本地时间不准会导致请求被拒。我的做法是请求前先同步一次网络时间,或者允许一定误差范围。

token这类会话级参数稍微麻烦点。它通常来自页面加载时的一次初始化请求,有效期有限。处理方式有两种:一是每次运行前先请求一次初始化接口拿 token,二是把 token 缓存起来定期刷新。我倾向第一种,简单可靠,多一次请求而已。

提示:token 的获取接口本身可能也有校验,如果发现拿不到 token,回头检查这个接口的请求头,往往缺了某个 Referer 或 User-Agent。

3.4 请求头的完整复刻

很多人算法还原对了,请求还是失败,问题出在请求头。服务端校验的不只是 sign,还有一整套请求头指纹。必须复刻的通常包括:

  • User-Agent:要和真实浏览器一致,版本号别太老
  • Referer:指向翻译页面,缺了容易被拒
  • Origin:跨域场景下需要
  • Cookie:会话标识,有些接口强依赖
  • Accept、Accept-Language:影响返回内容语言

我的做法是把抓包拿到的请求头整份复制,然后在代码里逐条设置,跑通之后再逐条删减,看哪些是必需的。这样既保证能跑通,又能精简到最小集合,减少维护成本。

4. 响应解析与异常处理

4.1 返回数据的结构映射

请求跑通后,返回的通常是 JSON。但别高兴太早,有些字段是编码过的,比如译文可能做了 Base64 或者自定义编码。我遇到的情况是部分字段直接明文、部分字段需要解码。处理方式是先打印完整响应,人工看一遍结构,把「看起来像密文」的字段挑出来单独处理。

字段映射建议做成配置,而不是硬编码在逻辑里。因为站点改版时字段名可能变,做成配置后改一处就行。我一般会维护一个字段路径表,比如result.translation对应译文,result.dict对应词典数据,解析时按路径取值,取不到就给默认值,避免 KeyError 直接崩掉。

4.2 常见错误码与应对

跑批量请求时,错误码是家常便饭。我整理了一份实测遇到的错误码对照:

错误现象可能原因排查方向
返回空或 403请求头缺失/UA 被识别补全 Referer、UA
sign 校验失败拼接顺序或编码错逐字段核对、对比真实 sign
频率限制请求过快加延时、降并发
token 失效会话过期重新获取 token
返回乱码编码不一致统一 UTF-8、检查响应头

这张表是我踩坑踩出来的,尤其是「sign 校验失败」这一条,十有八九是编码问题,而不是算法本身错。遇到校验失败,先别怀疑算法,先怀疑你喂进去的字符串和 JS 里喂进去的是不是同一个东西。

4.3 稳定性与频率控制

单次请求跑通不代表批量能用。我实测下来,连续快速请求很容易触发限制。控制策略有几个层次:单线程加随机延时(比如 0.5 到 1.5 秒随机)、限制并发数(不超过 2 到 3)、失败重试带退避(第一次等 2 秒,第二次等 5 秒,第三次放弃)。

注意:延时不要用固定值,固定间隔的请求模式很容易被识别。随机化是基本操作,但随机范围也别太夸张,否则整体速度太慢。

5. 实操心得与避坑清单

5.1 我踩过的几个典型坑

第一个坑是盲目相信抓包工具的「复制为 cURL」。复制出来的命令看着能跑,但里面的 Cookie 往往是临时的,过一会儿就失效。正确做法是把 Cookie 里的关键字段单独拎出来,搞清楚哪个是会话标识、哪个是设备标识,分别处理。

第二个坑是忽略请求体编码。POST 请求的 body 如果是 JSON,Content-Type 要设对;如果是表单,要注意 URL 编码。我有一次 body 里带了中文没编码,服务端直接返回参数错误,排查了半天才发现是编码问题。

第三个坑是算法还原后没做边界测试。空字符串、超长文本、特殊字符(emoji、换行)这些边界输入,往往能暴露编码处理的漏洞。建议还原完算法后,专门拿这几类输入跑一遍。

5.2 提高效率的几个习惯

分析时随手记笔记,把每个参数的来源、每个函数的入参出参都写下来,别指望脑子记。我一般开一个文本文件,边分析边记,最后这份笔记就是复现代码的蓝本。

多用 Console 做小实验,别在编辑器里改代码再刷新页面验证,太慢。Console 里直接调函数、看输出,几秒钟一个实验,效率高一个数量级。

遇到卡壳先放一放,换个接口或者换个时间段再回来看。有时候是站点临时调整,有时候是自己思路钻了牛角尖,隔一会儿再看往往豁然开朗。

5.3 关于合规与边界的提醒

这类分析仅限个人学习和技术研究,用于理解前端加密与请求校验的常见实现方式。实际使用时要遵守站点的服务条款,控制请求频率,不要对服务造成压力。技术本身是中性的,怎么用取决于人。我个人的原则是:只做学习验证,不做大规模采集,遇到明确的技术保护就止步。

最后分享一个小技巧:如果你在还原某个哈希算法时死活对不上,试试把 JS 里的输入字符串用console.log打印出来,和你在 Python 里拼出来的字符串逐字符对比(包括不可见字符)。我遇到过好几次是 JS 里多拼了一个看不见的分隔符,肉眼看不出来,逐字符对比才发现的。这个笨办法屡试不爽,值得养成习惯。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询