1. 项目概述当爬虫遇到加密的时间戳最近在做一个数据采集项目时遇到了一个典型的“拦路虎”目标网站的接口参数里有一个看似普通的timestamp字段但直接使用当前时间戳请求返回的永远是“签名错误”或“请求已过期”。这让我意识到这个时间戳被加密了。对于很多刚接触爬虫的朋友来说遇到这种参数加密尤其是时间戳这种动态变化的参数往往会感到无从下手。其实这恰恰是反爬策略从“简单屏蔽”升级到“动态验证”的标志也是爬虫工程师从入门到进阶必须跨过的一道坎。这个项目我们就来深入实战拆解如何逆向分析并破解这种接口参数中的时间戳加密。我们将以 Python 作为主要工具但核心思路是通用的适用于任何语言。整个过程不仅仅是找到加密算法更重要的是理解前端 JavaScript 的执行逻辑、如何定位关键代码、以及如何用 Python 完美复现这个动态生成过程。最终我们的目标是获得一个能够稳定、正确生成加密后时间戳参数的 Python 函数让爬虫请求畅通无阻。2. 逆向分析的核心思路与工具准备逆向分析前端加密听起来很高深其实核心思路就是“扮演浏览器”。浏览器能成功请求是因为它执行了完整的 JavaScript 代码生成了正确的参数。我们的任务就是弄清楚它到底执行了哪段代码以及这段代码的逻辑是什么。2.1 逆向分析的基本逻辑链条面对一个加密参数我们的分析路径应该是清晰的定位参数通过浏览器开发者工具F12的 Network 面板找到目标请求查看其请求参数Payload确认哪个是疑似被加密的时间戳字段通常叫t,timestamp,_t,sign等。搜索关键代码在 Sources 面板中全局搜索这个加密后的参数字符串或者搜索参数名如timestamp。如果加密逻辑是通用的可能会被封装在某个js文件里通过函数名如getSign,encryptTimestamp也能搜到。打断点与跟踪在疑似生成该参数的代码行设置断点重新触发请求。当代码执行到断点时程序会暂停此时我们可以查看调用栈Call Stack、观察局部变量的值、单步执行F10/F11一步步跟踪参数是如何从原始时间戳变成最终密文的。逻辑提取与复现理清加密逻辑后用 Python 代码将其复现出来。这可能涉及哈希算法如 MD5, SHA-1, SHA-256、对称加密如 AES、非对称加密如 RSA或自定义的位运算、字符串拼接等。注意整个过程需要耐心。前端代码可能被混淆Obfuscated变量名变成a, b, c逻辑被拆散。这时需要依靠对常见加密模式的熟悉和细致的调试来理清头绪。2.2 必备工具与环境搭建工欲善其事必先利其器。以下是本次实战需要用到的核心工具浏览器开发者工具Chrome 或 Edge 的 DevTools 是主力。重点关注Network网络请求监控、Sources源码查看与调试、Console控制台执行 JS 这三个面板。Node.js 环境有些加密库是 Node.js 特有的或者在浏览器环境中难以直接调试。安装 Node.js 后我们可以将关键的 JS 函数片段提取出来在本地用 Node.js 运行和测试验证我们的理解是否正确。这对于复现复杂加密逻辑至关重要。Python 环境及库确保你的 Python 环境已安装以下库requests: 用于发送 HTTP 请求。execjs: 一个非常关键的库它允许 Python 执行 JavaScript 代码。当加密逻辑过于复杂用纯 Python 复现困难或存在细微差异时可以直接调用原始的 JS 代码。hashlib,hmac: Python 标准库用于实现常见的哈希算法。Crypto(或pycryptodome): 如果需要 AES、RSA 等标准加密算法这个库是必备的。json,time,re等标准库辅助处理数据。安装命令示例pip install requests execjs pycryptodome实操心得在开始逆向之前先用浏览器正常手动操作一遍确保你能成功触发一次目标请求。同时留意是否有设置Cookie、User-Agent或特定的Header。这些信息在后续用 Python 模拟请求时同样重要缺失它们也可能导致请求失败。3. 实战案例拆解定位并分析时间戳加密逻辑我们以一个虚构但非常典型的场景为例目标网站api.example.com/data的 POST 请求需要携带参数timestamp和sign。其中timestamp看起来是加密后的字符串而非直接的 Unix 时间戳。3.1 网络抓包与初步观察打开浏览器开发者工具的 Network 面板勾选Preserve log保留日志然后进行触发数据加载的操作。找到目标请求api.example.com/data点击查看Headers和Payload。Request Headers注意是否有Authorization,X-Requested-With等特殊头。Request Payload我们可能看到类似这样的数据{ page: 1, size: 20, timestamp: a1b2c3d4e5f67890abcdef1234567890, sign: f0e1d2c3b4a59687fedcba9876543210 }这里的timestamp是一串十六进制字符串长度固定为32位这强烈暗示它可能是一个MD5或SHA-140位哈希值或者是 AES 加密后的结果。而sign字段通常是所有参数包括加密后的 timestamp按照一定规则拼接后再进行一次哈希运算得到的签名用于防止参数被篡改。3.2 搜索与定位加密函数在 Sources 面板中按CtrlShiftF进行全局搜索。我们可以尝试搜索直接搜索加密后的timestamp值a1b2c3d4e5f67890abcdef1234567890。如果这个值是在前端生成的有很大概率能直接定位到生成它的代码行。搜索参数名timestamp查看哪些 JS 文件包含对这个字段的赋值或处理逻辑。搜索常见的加密函数名或关键词如encrypt,MD5,SHA1,createHash,CryptoJS,getTimestamp,sign。假设我们搜索timestamp在一个名为app.xxxxxx.js的混淆文件中找到了如下代码片段function getEncryptedTimestamp() { var t (new Date).getTime(); var e encryptFunc(t.toString()); return e }这是一个非常清晰的线索它表明加密后的timestamp是由当前时间戳t经过一个叫encryptFunc的函数处理得到的。接下来我们的目标就是找到encryptFunc的定义。3.3 深入调试与逻辑还原在var e encryptFunc(t.toString());这一行点击行号设置断点。然后刷新页面或重新触发请求。当代码执行到断点时程序会暂停。查看变量值在右侧的 Scope 面板中可以看到此时变量t的值例如1715167890123。这是一个标准的13位毫秒级 Unix 时间戳。跟踪函数将鼠标悬停在encryptFunc上可能会显示其定义或者我们可以点击 Step into (F11) 进入这个函数内部。分析 encryptFunc进入后我们可能看到类似这样的代码function encryptFunc(input) { var key a_secret_key_123; var iv initial_vector_456; // 使用 CryptoJS 进行 AES-128-CBC 加密 var encrypted CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(input), CryptoJS.enc.Utf8.parse(key), { iv: CryptoJS.enc.Utf8.parse(iv), mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encrypted.ciphertext.toString(CryptoJS.enc.Hex); }太好了逻辑非常清晰将字符串化的时间戳用固定的密钥key和初始向量iv通过AES-128-CBC模式加密并将结果转换为十六进制字符串输出。排查技巧实录有时encryptFunc可能被进一步混淆或者 key/iv 是从其他接口动态获取的。这时需要查看调用栈在 Call Stack 面板中查看encryptFunc是被谁调用的向上回溯可能找到 key/iv 的生成或获取逻辑。搜索常量在 Sources 中搜索a_secret_key或CryptoJS.AES.encrypt来定位更多相关代码。控制台测试在 Console 面板中我们可以直接复制encryptFunc函数和相关依赖如 CryptoJS 对象然后手动调用encryptFunc(1715167890123)看输出是否与网络请求中的timestamp值一致。这是验证我们分析是否正确的最快方法。4. Python 复现加密逻辑的两种策略分析清楚 JS 逻辑后就到了用 Python 复现的环节。根据加密复杂程度我们有两种主要策略。4.1 策略一纯 Python 复现推荐如果加密算法是标准的如 MD5、SHA-256、AES、RSA 等优先使用 Python 的密码学库进行复现。这样做性能好依赖少也更“原生”。对于上面分析的 AES-128-CBC 加密Python 复现代码如下import time from Crypto.Cipher import AES from Crypto.Util.Padding import pad import binascii def encrypt_timestamp_pure_python(): 使用纯Python复现AES-128-CBC加密时间戳 # 1. 获取当前13位毫秒时间戳 raw_ts str(int(time.time() * 1000)) # 例如: 1715167890123 # 2. 定义密钥和IV与JS中完全一致 key ba_secret_key_123 # 注意AES-128密钥长度为16字节 iv binitial_vector_456 # IV长度也为16字节 # 3. 创建AES加密器模式为CBC cipher AES.new(key, AES.MODE_CBC, iv) # 4. 对明文进行PKCS7填充并加密 # 需要将字符串编码为字节并进行填充 plaintext_bytes raw_ts.encode(utf-8) padded_bytes pad(plaintext_bytes, AES.block_size) encrypted_bytes cipher.encrypt(padded_bytes) # 5. 将加密后的字节转换为十六进制字符串 encrypted_hex binascii.hexlify(encrypted_bytes).decode(utf-8) return encrypted_hex # 测试 if __name__ __main__: encrypted_ts encrypt_timestamp_pure_python() print(f生成的加密时间戳: {encrypted_ts}) # 可以与浏览器抓包得到的值进行对比关键点解析字节与字符串Python 的加密库操作的是字节bytes而 JS 中通常是字符串。encode()和decode()是关键。填充模式AES 是块加密需要填充到块大小的整数倍。必须确认 JS 使用的填充模式这里是 PKCS7并在 Python 中使用对应的填充函数pad。密钥和 IV必须确保与 JS 中的值完全一致包括长度。AES-128 的 key 是 16 字节。4.2 策略二使用 execjs 调用 JavaScript 代码当遇到非常复杂、自定义、或者严重依赖浏览器环境对象如window,document的加密逻辑时用 Python 完全重写可能事倍功半。此时execjs库是救星。它允许我们在 Python 中创建一个 JS 执行环境直接运行那段 JS 代码。首先我们需要将关键的 JS 函数提取出来保存为一个字符串或单独的.js文件。假设我们提取了如下代码到encrypt.js文件// encrypt.js const CryptoJS require(crypto-js); // 假设我们通过Node.js环境引入 function encryptFunc(input) { var key a_secret_key_123; var iv initial_vector_456; var encrypted CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(input), CryptoJS.enc.Utf8.parse(key), { iv: CryptoJS.enc.Utf8.parse(iv), mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encrypted.ciphertext.toString(CryptoJS.enc.Hex); } // 导出函数供Node.js或execjs使用 module.exports { encryptFunc };然后在 Python 中这样调用import time import execjs def encrypt_timestamp_via_js(): 通过execjs调用JS代码生成加密时间戳 # 1. 读取JS文件内容 with open(encrypt.js, r, encodingutf-8) as f: js_code f.read() # 2. 编译JS代码 ctx execjs.compile(js_code) # 3. 获取当前时间戳 raw_ts str(int(time.time() * 1000)) # 4. 调用JS函数 encrypted_ts ctx.call(encryptFunc, raw_ts) return encrypted_ts # 测试 if __name__ __main__: encrypted_ts encrypt_timestamp_via_js() print(f通过JS生成的加密时间戳: {encrypted_ts})实操心得与避坑指南环境依赖execjs背后需要有一个 JS 运行时。在 Windows 上它默认使用 JScript功能有限。强烈建议安装Node.js然后通过execjs.get().name检查当前运行时最好指定为Node.js。模块引入如果 JS 代码中使用了require(‘crypto-js’)你需要确保在运行 Python 脚本的机器上通过npm install crypto-js安装了该库。execjs会继承 Node.js 的环境。性能考虑频繁调用execjs创建上下文和执行 JS 会有性能开销。如果爬虫请求频率很高建议将时间戳的生成批量处理或者将核心 JS 逻辑用 Python 重写以提升效率。错误处理JS 代码执行出错时execjs会抛出异常。务必用try...except包裹并打印详细的错误信息这有助于调试 JS 环境或代码本身的问题。5. 整合到爬虫请求与签名验证成功生成加密时间戳后这通常只是第一步。很多接口还有签名sign校验。签名算法通常是将所有请求参数包括加密后的timestamp按照特定顺序如字典序拼接成一个字符串然后加上一个私钥secret再进行一次哈希运算如 MD5。5.1 生成完整签名示例假设接口要求参数按字母顺序排序后拼接然后进行 MD5 签名。import hashlib import urllib.parse def generate_sign(params, secret): 生成请求签名 :param params: dict, 所有请求参数包括加密后的timestamp :param secret: str, 签名密钥 :return: str, 计算得到的sign值 # 1. 对参数按键进行排序 sorted_params sorted(params.items(), keylambda x: x[0]) # 2. 拼接成 key1value1key2value2 的格式 query_string .join([f{k}{v} for k, v in sorted_params]) # 3. 在末尾拼接上密钥 sign_string query_string fsecret{secret} # 4. 计算MD5注意编码 m hashlib.md5() m.update(sign_string.encode(utf-8)) return m.hexdigest() # 模拟请求参数 params { page: 1, size: 20, timestamp: encrypt_timestamp_pure_python(), # 使用之前生成的加密时间戳 } secret_key your_api_secret_here sign generate_sign(params, secret_key) params[sign] sign # 将签名加入请求参数 print(f最终请求参数: {params})5.2 发送完整的请求现在我们可以使用requests库发送携带正确参数的请求了。import requests import json url https://api.example.com/data headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Content-Type: application/json, # 根据接口实际情况调整 } # 使用上面生成的 params response requests.post(url, headersheaders, datajson.dumps(params)) # 或者如果接口接受 form-data则使用requests.post(url, headersheaders, dataparams) if response.status_code 200: data response.json() print(请求成功数据:, data) else: print(f请求失败状态码: {response.status_code}, 响应: {response.text})6. 常见问题排查与进阶技巧即使按照步骤操作在实际复现过程中也可能遇到各种问题。这里记录一些常见的坑和排查思路。6.1 加密结果不一致问题排查表问题现象可能原因排查步骤与解决方案Python 和 JS 生成的密文完全不同。1.时间戳源不一致JS 用的是Date.now()Python 用的是time.time()可能存在微小误差。但更大的可能是格式字符串/数字或精度秒/毫秒问题。2.密钥/IV 不一致检查字符串是否完全一致包括不可见字符、编码UTF-8/ASCII。3.加密模式或填充错误确认是 CBC 还是 ECB是 PKCS7 还是 ZeroPadding4.输入数据格式JS 中CryptoJS.enc.Utf8.parse(input)将字符串转为 WordArrayPython 中需要input.encode(‘utf-8’)转为 bytes。1.固定输入在 JS 调试器中用一个固定字符串如”test123″作为输入记录输出。在 Python 中用同样的固定字符串输入对比输出。这样可以排除时间戳动态变化的影响。2.打印中间值在 JS 和 Python 中分别打印出密钥、IV、填充前的明文字节进行逐字节对比。3.查阅文档确认 CryptoJS 和 PyCryptodome 库对于相同模式和填充的实现是否完全等价。使用 execjs 调用时报错提示require未定义或模块找不到。1.运行时环境错误execjs未使用 Node.js 环境。2.Node.js 模块未安装JS 代码中引用的 npm 包如crypto-js在系统全局或当前目录下未安装。1. 安装 Node.js并在 Python 中指定运行时execjs.get(‘Node’).compile(js_code)。2. 在包含encrypt.js的目录下运行npm init -y和npm install crypto-js。或者使用execjs时将crypto-js的源码直接内联到 JS 代码字符串中避免require。请求返回“签名错误”但时间戳加密似乎对了。1.签名算法理解有误参数拼接顺序、是否包含 URL、是否对值进行了 URL 编码等细节出错。2.密钥错误用于签名的secret不对。3.参数遗漏或多余可能有些隐含参数如固定的appId也需要参与签名但被遗漏了。1.仔细对比用浏览器成功请求的原始数据和你 Python 代码生成的签名字符串进行逐字符对比。2.网络抓包验证在开发者工具的 Network 面板中查看请求的Payload或Query String Parameters确保你的 Python 代码生成的参数列表和顺序与之完全一致。3.控制台复现尝试在浏览器 Console 中用你的 Python 逻辑翻译成 JS计算一遍签名看是否与请求中的签名一致。6.2 应对代码混淆与动态密钥更高级的反爬可能会使用以下手段代码混淆变量和函数名被压缩成无意义的字符。应对方法是1) 依靠断点调试关注输入输出的值而不是变量名2) 搜索加密后结果的常量片段3) 使用浏览器插件或本地工具进行反混淆可读性提升有限。动态密钥加密用的 key 或 iv 可能不是硬编码在 JS 里的而是通过另一个接口获取或者由页面上的某些元素值计算得出。这时需要1) 在调用加密函数前设置断点查看 key/iv 的值是从哪个变量来的2) 在 Network 面板中搜索可能返回密钥的 XHR 请求3) 分析 key/iv 的生成算法可能也是由时间戳、Cookie 等计算得来需要一并复现。6.3 保持爬虫的健壮性与道德规范错误重试与超时网络请求可能失败接口可能暂时不可用。为你的请求添加重试机制如tenacity库和合理的超时设置。速率限制严格遵守网站的robots.txt协议如果有并主动限制请求频率避免对目标服务器造成过大压力。使用time.sleep()在请求间添加随机间隔。User-Agent 轮换使用一个常见的浏览器 User-Agent 列表进行轮换避免因单一 UA 被识别。处理 Cookie 和 Session对于需要登录的网站使用requests.Session()来保持会话状态自动处理 Cookie。明确数据用途爬取的数据请用于个人学习、分析或符合网站条款的合法用途。尊重数据版权和用户隐私。破解接口参数加密尤其是时间戳加密是一个需要耐心、细心和逻辑分析能力的过程。它没有一成不变的公式每个网站都可能不同。核心在于掌握“观察-定位-调试-复现”这一套方法论并熟练运用开发者工具和编程语言进行验证。当你成功绕过一道加密防线时获得的不仅是数据更是解决问题的能力和对网络通信更深的理解。
Python爬虫实战:逆向破解接口时间戳加密参数
发布时间:2026/7/17 21:31:58
1. 项目概述当爬虫遇到加密的时间戳最近在做一个数据采集项目时遇到了一个典型的“拦路虎”目标网站的接口参数里有一个看似普通的timestamp字段但直接使用当前时间戳请求返回的永远是“签名错误”或“请求已过期”。这让我意识到这个时间戳被加密了。对于很多刚接触爬虫的朋友来说遇到这种参数加密尤其是时间戳这种动态变化的参数往往会感到无从下手。其实这恰恰是反爬策略从“简单屏蔽”升级到“动态验证”的标志也是爬虫工程师从入门到进阶必须跨过的一道坎。这个项目我们就来深入实战拆解如何逆向分析并破解这种接口参数中的时间戳加密。我们将以 Python 作为主要工具但核心思路是通用的适用于任何语言。整个过程不仅仅是找到加密算法更重要的是理解前端 JavaScript 的执行逻辑、如何定位关键代码、以及如何用 Python 完美复现这个动态生成过程。最终我们的目标是获得一个能够稳定、正确生成加密后时间戳参数的 Python 函数让爬虫请求畅通无阻。2. 逆向分析的核心思路与工具准备逆向分析前端加密听起来很高深其实核心思路就是“扮演浏览器”。浏览器能成功请求是因为它执行了完整的 JavaScript 代码生成了正确的参数。我们的任务就是弄清楚它到底执行了哪段代码以及这段代码的逻辑是什么。2.1 逆向分析的基本逻辑链条面对一个加密参数我们的分析路径应该是清晰的定位参数通过浏览器开发者工具F12的 Network 面板找到目标请求查看其请求参数Payload确认哪个是疑似被加密的时间戳字段通常叫t,timestamp,_t,sign等。搜索关键代码在 Sources 面板中全局搜索这个加密后的参数字符串或者搜索参数名如timestamp。如果加密逻辑是通用的可能会被封装在某个js文件里通过函数名如getSign,encryptTimestamp也能搜到。打断点与跟踪在疑似生成该参数的代码行设置断点重新触发请求。当代码执行到断点时程序会暂停此时我们可以查看调用栈Call Stack、观察局部变量的值、单步执行F10/F11一步步跟踪参数是如何从原始时间戳变成最终密文的。逻辑提取与复现理清加密逻辑后用 Python 代码将其复现出来。这可能涉及哈希算法如 MD5, SHA-1, SHA-256、对称加密如 AES、非对称加密如 RSA或自定义的位运算、字符串拼接等。注意整个过程需要耐心。前端代码可能被混淆Obfuscated变量名变成a, b, c逻辑被拆散。这时需要依靠对常见加密模式的熟悉和细致的调试来理清头绪。2.2 必备工具与环境搭建工欲善其事必先利其器。以下是本次实战需要用到的核心工具浏览器开发者工具Chrome 或 Edge 的 DevTools 是主力。重点关注Network网络请求监控、Sources源码查看与调试、Console控制台执行 JS 这三个面板。Node.js 环境有些加密库是 Node.js 特有的或者在浏览器环境中难以直接调试。安装 Node.js 后我们可以将关键的 JS 函数片段提取出来在本地用 Node.js 运行和测试验证我们的理解是否正确。这对于复现复杂加密逻辑至关重要。Python 环境及库确保你的 Python 环境已安装以下库requests: 用于发送 HTTP 请求。execjs: 一个非常关键的库它允许 Python 执行 JavaScript 代码。当加密逻辑过于复杂用纯 Python 复现困难或存在细微差异时可以直接调用原始的 JS 代码。hashlib,hmac: Python 标准库用于实现常见的哈希算法。Crypto(或pycryptodome): 如果需要 AES、RSA 等标准加密算法这个库是必备的。json,time,re等标准库辅助处理数据。安装命令示例pip install requests execjs pycryptodome实操心得在开始逆向之前先用浏览器正常手动操作一遍确保你能成功触发一次目标请求。同时留意是否有设置Cookie、User-Agent或特定的Header。这些信息在后续用 Python 模拟请求时同样重要缺失它们也可能导致请求失败。3. 实战案例拆解定位并分析时间戳加密逻辑我们以一个虚构但非常典型的场景为例目标网站api.example.com/data的 POST 请求需要携带参数timestamp和sign。其中timestamp看起来是加密后的字符串而非直接的 Unix 时间戳。3.1 网络抓包与初步观察打开浏览器开发者工具的 Network 面板勾选Preserve log保留日志然后进行触发数据加载的操作。找到目标请求api.example.com/data点击查看Headers和Payload。Request Headers注意是否有Authorization,X-Requested-With等特殊头。Request Payload我们可能看到类似这样的数据{ page: 1, size: 20, timestamp: a1b2c3d4e5f67890abcdef1234567890, sign: f0e1d2c3b4a59687fedcba9876543210 }这里的timestamp是一串十六进制字符串长度固定为32位这强烈暗示它可能是一个MD5或SHA-140位哈希值或者是 AES 加密后的结果。而sign字段通常是所有参数包括加密后的 timestamp按照一定规则拼接后再进行一次哈希运算得到的签名用于防止参数被篡改。3.2 搜索与定位加密函数在 Sources 面板中按CtrlShiftF进行全局搜索。我们可以尝试搜索直接搜索加密后的timestamp值a1b2c3d4e5f67890abcdef1234567890。如果这个值是在前端生成的有很大概率能直接定位到生成它的代码行。搜索参数名timestamp查看哪些 JS 文件包含对这个字段的赋值或处理逻辑。搜索常见的加密函数名或关键词如encrypt,MD5,SHA1,createHash,CryptoJS,getTimestamp,sign。假设我们搜索timestamp在一个名为app.xxxxxx.js的混淆文件中找到了如下代码片段function getEncryptedTimestamp() { var t (new Date).getTime(); var e encryptFunc(t.toString()); return e }这是一个非常清晰的线索它表明加密后的timestamp是由当前时间戳t经过一个叫encryptFunc的函数处理得到的。接下来我们的目标就是找到encryptFunc的定义。3.3 深入调试与逻辑还原在var e encryptFunc(t.toString());这一行点击行号设置断点。然后刷新页面或重新触发请求。当代码执行到断点时程序会暂停。查看变量值在右侧的 Scope 面板中可以看到此时变量t的值例如1715167890123。这是一个标准的13位毫秒级 Unix 时间戳。跟踪函数将鼠标悬停在encryptFunc上可能会显示其定义或者我们可以点击 Step into (F11) 进入这个函数内部。分析 encryptFunc进入后我们可能看到类似这样的代码function encryptFunc(input) { var key a_secret_key_123; var iv initial_vector_456; // 使用 CryptoJS 进行 AES-128-CBC 加密 var encrypted CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(input), CryptoJS.enc.Utf8.parse(key), { iv: CryptoJS.enc.Utf8.parse(iv), mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encrypted.ciphertext.toString(CryptoJS.enc.Hex); }太好了逻辑非常清晰将字符串化的时间戳用固定的密钥key和初始向量iv通过AES-128-CBC模式加密并将结果转换为十六进制字符串输出。排查技巧实录有时encryptFunc可能被进一步混淆或者 key/iv 是从其他接口动态获取的。这时需要查看调用栈在 Call Stack 面板中查看encryptFunc是被谁调用的向上回溯可能找到 key/iv 的生成或获取逻辑。搜索常量在 Sources 中搜索a_secret_key或CryptoJS.AES.encrypt来定位更多相关代码。控制台测试在 Console 面板中我们可以直接复制encryptFunc函数和相关依赖如 CryptoJS 对象然后手动调用encryptFunc(1715167890123)看输出是否与网络请求中的timestamp值一致。这是验证我们分析是否正确的最快方法。4. Python 复现加密逻辑的两种策略分析清楚 JS 逻辑后就到了用 Python 复现的环节。根据加密复杂程度我们有两种主要策略。4.1 策略一纯 Python 复现推荐如果加密算法是标准的如 MD5、SHA-256、AES、RSA 等优先使用 Python 的密码学库进行复现。这样做性能好依赖少也更“原生”。对于上面分析的 AES-128-CBC 加密Python 复现代码如下import time from Crypto.Cipher import AES from Crypto.Util.Padding import pad import binascii def encrypt_timestamp_pure_python(): 使用纯Python复现AES-128-CBC加密时间戳 # 1. 获取当前13位毫秒时间戳 raw_ts str(int(time.time() * 1000)) # 例如: 1715167890123 # 2. 定义密钥和IV与JS中完全一致 key ba_secret_key_123 # 注意AES-128密钥长度为16字节 iv binitial_vector_456 # IV长度也为16字节 # 3. 创建AES加密器模式为CBC cipher AES.new(key, AES.MODE_CBC, iv) # 4. 对明文进行PKCS7填充并加密 # 需要将字符串编码为字节并进行填充 plaintext_bytes raw_ts.encode(utf-8) padded_bytes pad(plaintext_bytes, AES.block_size) encrypted_bytes cipher.encrypt(padded_bytes) # 5. 将加密后的字节转换为十六进制字符串 encrypted_hex binascii.hexlify(encrypted_bytes).decode(utf-8) return encrypted_hex # 测试 if __name__ __main__: encrypted_ts encrypt_timestamp_pure_python() print(f生成的加密时间戳: {encrypted_ts}) # 可以与浏览器抓包得到的值进行对比关键点解析字节与字符串Python 的加密库操作的是字节bytes而 JS 中通常是字符串。encode()和decode()是关键。填充模式AES 是块加密需要填充到块大小的整数倍。必须确认 JS 使用的填充模式这里是 PKCS7并在 Python 中使用对应的填充函数pad。密钥和 IV必须确保与 JS 中的值完全一致包括长度。AES-128 的 key 是 16 字节。4.2 策略二使用 execjs 调用 JavaScript 代码当遇到非常复杂、自定义、或者严重依赖浏览器环境对象如window,document的加密逻辑时用 Python 完全重写可能事倍功半。此时execjs库是救星。它允许我们在 Python 中创建一个 JS 执行环境直接运行那段 JS 代码。首先我们需要将关键的 JS 函数提取出来保存为一个字符串或单独的.js文件。假设我们提取了如下代码到encrypt.js文件// encrypt.js const CryptoJS require(crypto-js); // 假设我们通过Node.js环境引入 function encryptFunc(input) { var key a_secret_key_123; var iv initial_vector_456; var encrypted CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(input), CryptoJS.enc.Utf8.parse(key), { iv: CryptoJS.enc.Utf8.parse(iv), mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encrypted.ciphertext.toString(CryptoJS.enc.Hex); } // 导出函数供Node.js或execjs使用 module.exports { encryptFunc };然后在 Python 中这样调用import time import execjs def encrypt_timestamp_via_js(): 通过execjs调用JS代码生成加密时间戳 # 1. 读取JS文件内容 with open(encrypt.js, r, encodingutf-8) as f: js_code f.read() # 2. 编译JS代码 ctx execjs.compile(js_code) # 3. 获取当前时间戳 raw_ts str(int(time.time() * 1000)) # 4. 调用JS函数 encrypted_ts ctx.call(encryptFunc, raw_ts) return encrypted_ts # 测试 if __name__ __main__: encrypted_ts encrypt_timestamp_via_js() print(f通过JS生成的加密时间戳: {encrypted_ts})实操心得与避坑指南环境依赖execjs背后需要有一个 JS 运行时。在 Windows 上它默认使用 JScript功能有限。强烈建议安装Node.js然后通过execjs.get().name检查当前运行时最好指定为Node.js。模块引入如果 JS 代码中使用了require(‘crypto-js’)你需要确保在运行 Python 脚本的机器上通过npm install crypto-js安装了该库。execjs会继承 Node.js 的环境。性能考虑频繁调用execjs创建上下文和执行 JS 会有性能开销。如果爬虫请求频率很高建议将时间戳的生成批量处理或者将核心 JS 逻辑用 Python 重写以提升效率。错误处理JS 代码执行出错时execjs会抛出异常。务必用try...except包裹并打印详细的错误信息这有助于调试 JS 环境或代码本身的问题。5. 整合到爬虫请求与签名验证成功生成加密时间戳后这通常只是第一步。很多接口还有签名sign校验。签名算法通常是将所有请求参数包括加密后的timestamp按照特定顺序如字典序拼接成一个字符串然后加上一个私钥secret再进行一次哈希运算如 MD5。5.1 生成完整签名示例假设接口要求参数按字母顺序排序后拼接然后进行 MD5 签名。import hashlib import urllib.parse def generate_sign(params, secret): 生成请求签名 :param params: dict, 所有请求参数包括加密后的timestamp :param secret: str, 签名密钥 :return: str, 计算得到的sign值 # 1. 对参数按键进行排序 sorted_params sorted(params.items(), keylambda x: x[0]) # 2. 拼接成 key1value1key2value2 的格式 query_string .join([f{k}{v} for k, v in sorted_params]) # 3. 在末尾拼接上密钥 sign_string query_string fsecret{secret} # 4. 计算MD5注意编码 m hashlib.md5() m.update(sign_string.encode(utf-8)) return m.hexdigest() # 模拟请求参数 params { page: 1, size: 20, timestamp: encrypt_timestamp_pure_python(), # 使用之前生成的加密时间戳 } secret_key your_api_secret_here sign generate_sign(params, secret_key) params[sign] sign # 将签名加入请求参数 print(f最终请求参数: {params})5.2 发送完整的请求现在我们可以使用requests库发送携带正确参数的请求了。import requests import json url https://api.example.com/data headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Content-Type: application/json, # 根据接口实际情况调整 } # 使用上面生成的 params response requests.post(url, headersheaders, datajson.dumps(params)) # 或者如果接口接受 form-data则使用requests.post(url, headersheaders, dataparams) if response.status_code 200: data response.json() print(请求成功数据:, data) else: print(f请求失败状态码: {response.status_code}, 响应: {response.text})6. 常见问题排查与进阶技巧即使按照步骤操作在实际复现过程中也可能遇到各种问题。这里记录一些常见的坑和排查思路。6.1 加密结果不一致问题排查表问题现象可能原因排查步骤与解决方案Python 和 JS 生成的密文完全不同。1.时间戳源不一致JS 用的是Date.now()Python 用的是time.time()可能存在微小误差。但更大的可能是格式字符串/数字或精度秒/毫秒问题。2.密钥/IV 不一致检查字符串是否完全一致包括不可见字符、编码UTF-8/ASCII。3.加密模式或填充错误确认是 CBC 还是 ECB是 PKCS7 还是 ZeroPadding4.输入数据格式JS 中CryptoJS.enc.Utf8.parse(input)将字符串转为 WordArrayPython 中需要input.encode(‘utf-8’)转为 bytes。1.固定输入在 JS 调试器中用一个固定字符串如”test123″作为输入记录输出。在 Python 中用同样的固定字符串输入对比输出。这样可以排除时间戳动态变化的影响。2.打印中间值在 JS 和 Python 中分别打印出密钥、IV、填充前的明文字节进行逐字节对比。3.查阅文档确认 CryptoJS 和 PyCryptodome 库对于相同模式和填充的实现是否完全等价。使用 execjs 调用时报错提示require未定义或模块找不到。1.运行时环境错误execjs未使用 Node.js 环境。2.Node.js 模块未安装JS 代码中引用的 npm 包如crypto-js在系统全局或当前目录下未安装。1. 安装 Node.js并在 Python 中指定运行时execjs.get(‘Node’).compile(js_code)。2. 在包含encrypt.js的目录下运行npm init -y和npm install crypto-js。或者使用execjs时将crypto-js的源码直接内联到 JS 代码字符串中避免require。请求返回“签名错误”但时间戳加密似乎对了。1.签名算法理解有误参数拼接顺序、是否包含 URL、是否对值进行了 URL 编码等细节出错。2.密钥错误用于签名的secret不对。3.参数遗漏或多余可能有些隐含参数如固定的appId也需要参与签名但被遗漏了。1.仔细对比用浏览器成功请求的原始数据和你 Python 代码生成的签名字符串进行逐字符对比。2.网络抓包验证在开发者工具的 Network 面板中查看请求的Payload或Query String Parameters确保你的 Python 代码生成的参数列表和顺序与之完全一致。3.控制台复现尝试在浏览器 Console 中用你的 Python 逻辑翻译成 JS计算一遍签名看是否与请求中的签名一致。6.2 应对代码混淆与动态密钥更高级的反爬可能会使用以下手段代码混淆变量和函数名被压缩成无意义的字符。应对方法是1) 依靠断点调试关注输入输出的值而不是变量名2) 搜索加密后结果的常量片段3) 使用浏览器插件或本地工具进行反混淆可读性提升有限。动态密钥加密用的 key 或 iv 可能不是硬编码在 JS 里的而是通过另一个接口获取或者由页面上的某些元素值计算得出。这时需要1) 在调用加密函数前设置断点查看 key/iv 的值是从哪个变量来的2) 在 Network 面板中搜索可能返回密钥的 XHR 请求3) 分析 key/iv 的生成算法可能也是由时间戳、Cookie 等计算得来需要一并复现。6.3 保持爬虫的健壮性与道德规范错误重试与超时网络请求可能失败接口可能暂时不可用。为你的请求添加重试机制如tenacity库和合理的超时设置。速率限制严格遵守网站的robots.txt协议如果有并主动限制请求频率避免对目标服务器造成过大压力。使用time.sleep()在请求间添加随机间隔。User-Agent 轮换使用一个常见的浏览器 User-Agent 列表进行轮换避免因单一 UA 被识别。处理 Cookie 和 Session对于需要登录的网站使用requests.Session()来保持会话状态自动处理 Cookie。明确数据用途爬取的数据请用于个人学习、分析或符合网站条款的合法用途。尊重数据版权和用户隐私。破解接口参数加密尤其是时间戳加密是一个需要耐心、细心和逻辑分析能力的过程。它没有一成不变的公式每个网站都可能不同。核心在于掌握“观察-定位-调试-复现”这一套方法论并熟练运用开发者工具和编程语言进行验证。当你成功绕过一道加密防线时获得的不仅是数据更是解决问题的能力和对网络通信更深的理解。