ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JavaScript逆向实战:破解有道翻译接口签名,实现Python自动化调用

JavaScript逆向实战:破解有道翻译接口签名,实现Python自动化调用 1. 项目概述与核心价值最近在做一个需要多语言支持的小工具自然想到了集成翻译功能。市面上成熟的翻译API不少但要么收费要么有调用频率限制。于是我把目光投向了那些提供免费网页翻译服务的大厂比如有道翻译。直接抓取其网页版的翻译接口看起来是个经济实惠的方案。但事情显然没这么简单点开浏览器开发者工具你会发现每次翻译请求都伴随着一堆看不懂的加密参数尤其是那个关键的sign签名。这就是典型的JavaScript 逆向场景前端页面通过 JavaScript 代码对请求参数进行加密或混淆以保护其接口不被轻易滥用或爬取。我们的目标就是拨开这层迷雾理解其加密逻辑并用我们自己的代码比如 Python复现这个过程从而能够稳定、自由地调用这个“隐藏”的接口。这个过程的价值远不止于“白嫖”一个翻译接口。对于前端开发它能让你深刻理解数据安全传输的实践对于测试工程师它是进行接口安全测试、漏洞挖掘的基础技能而对于爬虫工程师或数据开发者这更是绕过反爬机制、获取结构化数据的核心能力。通过逆向有道翻译这个具体案例我们可以系统地学习如何分析网络请求、定位关键加密函数、理解加密算法如 MD5并最终完成整个调用链的复现。这就像拿到了一把万能钥匙能打开许多类似的前端加密接口的大门。2. 逆向分析的核心思路与准备工作逆向不是盲目地试错而是一场有计划的“侦查”。我们的核心思路是从结果反推过程从现象定位源码。具体来说就是通过浏览器开发者工具捕获一次完整的、成功的翻译请求然后去寻找生成这个请求中加密参数的 JavaScript 代码。2.1 环境与工具准备工欲善其事必先利其器。以下是本次逆向分析的核心工具栈浏览器与开发者工具推荐使用Google Chrome或Microsoft Edge。它们的开发者工具F12 打开功能强大且统一是我们最主要的战场。重点关注Network网络和Sources源代码面板。抓包工具虽然浏览器自带的 Network 面板已经足够强大但专业的抓包工具如Fiddler或Charles在设置断点、修改请求、批量测试时更为方便。不过对于本次入门级逆向浏览器工具足矣。代码调试与格式化工具网页上的 JavaScript 经常被压缩Minify成一行难以阅读。Chrome 开发者工具的 Sources 面板自带格式化按钮{}符号可以一键将混淆的代码变得结构清晰。编程环境最终我们需要用其他语言复现加密逻辑。这里我选择Python 3因为它有丰富的库支持网络请求和加密计算。你需要安装requests库用于发送 HTTP 请求。2.2 关键线索网络请求分析首先我们打开有道翻译的网页版例如fanyi.youdao.com在输入框输入一个测试词比如 “hello”然后按下回车或点击翻译。紧接着迅速打开开发者工具的Network面板。你会看到一系列的网络请求。我们需要找到那个真正返回翻译结果的接口。通常可以通过以下特征筛选请求方法 大多是POST。请求URL 可能包含translate、api等关键词。对于有道通常是类似https://dict.youdao.com/webtranslate的地址。请求负载Payload 点击该请求查看Headers选项卡下的Form Data或Request Payload。这里会有一堆参数。以有道翻译为例你可能会看到一个POST请求其Form Data包含如下关键字段i: hello from: AUTO to: AUTO smartresult: dict client: fanyideskweb salt: 1723812345678 sign: 7a8b9c0d1e2f3a4b5c6d7e8f90123456 lts: 1723812345678 bv: 5a6b7c8d9e0f1a2b3c4d5e6f78901234 ...这里的i是待翻译文本from和to是语言方向这些都很直观。但salt盐值、sign签名、lts时间戳、bv浏览器指纹这些字段就是我们需要攻克的加密参数。尤其是sign它通常是其他参数按照特定规则组合后再经过加密算法如 MD5计算得出的用于服务端验证请求的合法性。注意不同时期、不同版本的有道翻译网页其接口地址和参数名可能发生变化。本文以某一典型版本为例讲解通用方法你需要以自己实际抓包看到的数据为准。3. 定位并解析核心加密函数找到了加密参数下一步就是找到生成它们的“工厂”——JavaScript 函数。3.1 全局搜索与断点调试在开发者工具的Sources面板中有一个全局搜索功能快捷键CtrlShiftF。我们可以用加密参数名作为关键词进行搜索。例如搜索sign:或sign 。更有效的方法是搜索可能参与加密计算的常量或函数名。既然我们怀疑是 MD5 加密可以直接搜索MD5或md5。或者搜索sign被赋值的地方sign 。搜索后你可能会定位到一段被压缩的 JS 代码。点击结果使用格式化工具{}美化代码。现在代码变得可读了。我们需要在这段代码中寻找生成sign的逻辑。通常sign的计算会涉及i文本、salt盐值、lts时间戳以及一个固定的密钥。代码可能长这样var sign n.md5(fanyideskweb e i Ygy_4cr#e#4EX^NUGUc5);或者更复杂一些。这里的e可能是salti是待翻译文本。为了确认我们的猜想需要设置断点进行动态调试。在疑似生成sign的代码行号上点击设置一个断点。然后回到网页再次触发一次翻译动作。此时代码执行会在断点处暂停。在右侧的Scope或Console面板中你可以查看此时各个变量的值。将鼠标悬停在变量上或直接在 Console 中输入变量名就能看到它们的实际内容。对比这些内容和最终网络请求中的参数值你就能完全确认加密逻辑。3.2 拆解加密逻辑假设我们确认了加密逻辑如下这是简化示例真实情况可能更复杂lts 是一个 13 位的时间戳毫秒级如Date.now()。salt 是lts加上一个随机整数比如 0-9也可能是lts本身。sign 是字符串fanyideskweb 待翻译文本 salt 一个固定的密钥的 MD5 值。bv 可能是对navigator.appVersion进行 MD5 加密后的值用于标识浏览器。我们需要把这些逻辑清晰地记录下来。关键在于理解字符串拼接的顺序和参与计算的固定字符串。一个字符的错误都会导致签名校验失败。实操心得在逆向时不要只盯着一个请求。多尝试翻译不同的内容短词、长句、中文、英文观察salt和sign的变化规律。如果salt只是时间戳那么相邻请求的salt会递增如果sign随文本变化那么加密逻辑一定包含了文本。通过对比分析可以更快地验证你的猜想。4. 使用 Python 复现加密与接口调用理解了逻辑就可以用 Python 来复现了。我们将使用hashlib库进行 MD5 计算用requests库发送请求。4.1 构建加密函数首先我们根据分析出的逻辑编写一个生成签名sign的函数。import hashlib import time import random def youdao_sign(text, ltsNone, saltNone): 生成有道翻译接口的 sign 签名 :param text: 待翻译文本 :param lts: 时间戳毫秒。如果为None则自动生成当前时间戳。 :param salt: 盐值。如果为None则根据 lts 生成。 :return: 包含 lts, salt, sign 的字典 # 1. 生成 lts (13位毫秒时间戳) if lts is None: lts str(int(time.time() * 1000)) # 2. 生成 salt (通常是 lts 加上一个随机数) if salt is None: # 观察到的规律salt 有时是 lts 末尾加一位随机数 salt lts str(random.randint(0, 9)) # 3. 定义固定字符串密钥这个值需要从JS代码中确认 # 示例密钥实际需替换为逆向得到的真实值 secret_key Ygy_4cr#e#4EX^NUGUc5 # 4. 拼接签名字符串 # 顺序非常重要常见格式 client text salt secret_key sign_str ffanyideskweb{text}{salt}{secret_key} # 5. 计算 MD5 m hashlib.md5() m.update(sign_str.encode(utf-8)) sign m.hexdigest() # 6. 生成 bv (浏览器指纹通常是对 navigator.appVersion 的MD5这里可以固定一个值) # 可以先从一次成功请求中复制一个固定的 bv 值来用 bv 5a6b7c8d9e0f1a2b3c4d5e6f78901234 # 示例值需替换 return { lts: lts, salt: salt, sign: sign, bv: bv } # 测试加密函数 text_to_translate hello encrypted_params youdao_sign(text_to_translate) print(f文本: {text_to_translate}) print(f生成参数: {encrypted_params})4.2 组装请求并调用接口有了加密参数我们就可以组装完整的 POST 请求了。import requests import json def translate_youdao(text, from_langAUTO, to_langAUTO): 调用有道翻译网页版接口进行翻译 # 目标接口URL (需根据实际抓包结果更新) url https://dict.youdao.com/webtranslate # 生成加密参数 encrypted youdao_sign(text) # 组装表单数据 form_data { i: text, # 待翻译文本 from: from_lang, # 源语言 to: to_lang, # 目标语言 smartresult: dict, # 固定值 client: fanyideskweb, # 固定值 salt: encrypted[salt], # 盐值 sign: encrypted[sign], # 签名 lts: encrypted[lts], # 时间戳 bv: encrypted[bv], # 浏览器指纹 doctype: json, # 期望返回格式 version: 2.1, # 版本 keyfrom: fanyi.web, # 固定值 action: FY_BY_REALTlME, # 固定值 # 可能还有其他固定参数... } # 设置请求头模拟浏览器 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36, Referer: https://fanyi.youdao.com/, Content-Type: application/x-www-form-urlencoded; charsetUTF-8, # Cookie: ... # 有时需要携带Cookie但简单翻译可能不需要 } try: # 发送 POST 请求 response requests.post(url, dataform_data, headersheaders, timeout5) response.raise_for_status() # 检查请求是否成功 # 有道返回的数据可能是加密的需要解密。这里假设返回的是明文JSON。 # 实际中返回结果可能是一个加密字符串需要进一步解密。 result response.json() # 解析结果结构需根据实际返回调整 # 例如翻译结果可能在 result[translateResult][0][0][tgt] if translateResult in result: translated_text result[translateResult][0][0][tgt] return translated_text else: print(未找到翻译结果原始响应:, result) return None except requests.exceptions.RequestException as e: print(f网络请求错误: {e}) return None except json.JSONDecodeError as e: print(fJSON解析错误响应内容可能已加密: {e}) print(原始响应文本:, response.text[:200]) # 打印前200字符看看 # 这里可能需要额外的解密步骤 return None # 执行翻译 if __name__ __main__: result translate_youdao(Hello, world!) if result: print(f翻译结果: {result})4.3 处理可能的响应加密值得注意的是有些接口为了进一步保护数据返回的响应体也是加密的比如 Base64 编码后再进行自定义的字符替换。如果你发现response.json()报错或者解析出的内容是一串乱码那么就需要在 JS 代码中继续寻找响应解密的逻辑。寻找方法与寻找请求加密逻辑类似在 Network 面板查看成功请求的Response如果是一串看似无规律的字符那基本就是加密了。然后回到 Sources 面板搜索处理这个响应的代码可以搜索接口返回的关键字段名或者搜索JSON.parse、decrypt等函数。找到解密函数后用 Python 同样复现其逻辑对response.text进行解密然后再解析为 JSON。5. 常见问题排查与实战技巧即使完全按照分析出的逻辑编写代码第一次调用也大概率会失败。以下是几个常见的坑点和排查思路。5.1 签名校验失败 (Sign Error)这是最常见的问题。表现为返回错误码或提示“签名错误”。检查字符串拼接顺序和内容 这是最可能出错的地方。确保拼接sign的每一个部分client,text,salt,secret_key都完全正确包括大小写和是否有空格。务必与在浏览器断点调试时看到的拼接结果逐字符对比。检查 MD5 计算的输入编码 在 Python 中md5.update()需要传入bytes类型。确保你的拼接字符串是用.encode(utf-8)进行编码的。其他编码如gbk会导致结果不同。确认密钥 (secret_key) 是否正确 密钥可能不是固定的有时会根据时间或其他因素变化。确保你从最新版的网页 JS 代码中提取了正确的密钥。检查salt的生成规则salt可能不是简单的lts随机数可能有更复杂的规则。多抓几次包分析salt与lts的数学关系。5.2 请求被拒绝或返回空数据请求头 (Headers) 不完整 有些接口会校验User-Agent、Referer、Origin甚至Cookie。用你的 Python 脚本发送请求时尽量模拟浏览器发送的完整请求头。可以使用开发者工具直接复制一次成功请求的Request Headers。缺少必要的 Cookie 某些接口需要登录态或特定的会话 Cookie。你可能需要先用requests.Session()对象访问一下翻译主页获取初始 Cookie然后再发送翻译请求。频率限制 免费接口通常有频率限制。如果你的请求太快可能会被暂时屏蔽。在代码中增加延时time.sleep是必要的礼貌。接口已更新 网站前端代码更新了加密逻辑发生了变化。你需要重新进行一遍抓包和逆向分析。这也是为什么自己逆向的接口不如官方 API 稳定的原因。5.3 响应数据无法解析响应被加密 如前所述返回的可能是加密文本。你需要找到并复现解密函数。返回格式非 JSON 检查Response Headers中的Content-Type。有时可能是纯文本或其他格式需要用相应的方法解析。5.4 实战技巧速查表问题现象可能原因排查步骤返回{errorCode:50}等错误码签名 (sign) 错误1. 断点调试 JS确认拼接字符串和最终sign。2. 用 Python 生成sign与浏览器生成的进行对比。3. 检查时间戳 (lts) 格式是否为13位毫秒。请求返回空或403 Forbidden请求头或 Cookie 问题1. 在代码中完整复制浏览器的请求头。2. 使用requests.Session()保持会话。3. 检查Referer和Origin头是否正确。json.decoder.JSONDecodeError响应体被加密或非JSON1. 查看浏览器中该请求的Preview或Response选项卡看是否是明文JSON。2. 如果是乱码在 JS 中搜索解密函数。前几次成功后来失败IP 或频率被限制1. 降低请求频率加入随机延时。2. 考虑使用代理 IP 池需注意合规性。代码突然全部失效网站接口或加密逻辑更新重新抓包从头开始分析。关注主要的 JS 文件是否有更新。核心心得逆向的本质是“模仿”。你的代码行为必须与浏览器中的 JavaScript 代码完全一致。任何细微的差别比如多一个空格、编码不同、时间戳精度不对都可能导致失败。耐心对比、逐行调试、大胆假设、小心验证是解决所有问题的钥匙。6. 扩展思考与安全建议通过这个案例我们成功实现了一个简单的接口逆向。但这仅仅是开始。现实中大型网站的加密策略要复杂得多混淆与加固 关键的 JavaScript 代码会被混淆变量名替换、代码控制流扁平化等甚至使用WebAssembly或VMP进行加固让静态分析变得极其困难。动态密钥 加密密钥可能不是硬编码在 JS 文件里而是由服务器动态下发每次会话都不同。环境检测 前端会检测代码是否在真实的浏览器环境中运行如检查window、document对象检测常见的自动化工具特征如果发现是爬虫则返回假数据或直接屏蔽。反调试 代码中会加入反调试逻辑一旦打开开发者工具就自动跳转或死循环。对于更复杂的场景可能需要使用自动化工具 如Puppeteer、Playwright或Selenium直接控制无头浏览器来执行 JS获取渲染后的结果。这能绕过大部分前端检测但资源消耗大、速度慢。深入 JS 虚拟机 使用PyExecJS、Node.js等直接执行关键的、复杂的加密 JS 函数片段。协议分析 尝试理解其整个通信协议寻找是否有更底层的、未加密的接口通常更难。最后必须强调安全与合规。本文所有技术讨论仅用于学习 JavaScript 运行机制、前端安全技术和接口交互原理。在实际应用中务必尊重robots.txt 遵守网站关于爬虫的声明。控制请求频率 避免对目标服务器造成负载压力这既是道德要求也能避免你的 IP 被封锁。查看服务条款 明确了解目标网站是否允许通过自动化方式访问其服务。用于合法目的 确保你的数据获取和使用方式符合相关法律法规。
返回列表