1. 招聘网站数据接口逆向分析实战
最近在研究某招聘网站的数据采集时,遇到了三个关键加密参数:businessData、e-sign和e-content-path。这三个参数都采用了不同的加密方式,且都会随请求变化。经过一番折腾,终于成功逆向出完整的加密逻辑。下面就把我的分析过程和解决方案分享给大家。
1.1 目标网站加密特点分析
这个招聘网站采用了比较完善的反爬机制,主要体现在以下几个方面:
- 核心数据接口使用了三个动态加密参数
- businessData参数采用SM4加密
- e-sign参数采用SM3哈希
- e-content-path参数涉及SM2非对称加密
- 前端代码使用了Webpack打包,增加了分析难度
提示:在实际逆向过程中,建议使用Chrome开发者工具的"条件断点"功能,可以大幅提高调试效率。
2. 接口分析与参数定位
2.1 数据接口识别
首先通过浏览器开发者工具观察页面加载过程:
- 打开网站首页,使用无痕模式避免缓存干扰
- 点击任意职位分类(如"IT/互联网 > 测试工程师")
- 在Network面板筛选XHR请求
经过多次测试发现,真实的数据请求接口会携带三个关键参数:
POST /api/job/search HTTP/1.1 Host: xxx.com Content-Type: application/json { "businessData": "a1b2c3...", "e-sign": "d4e5f6...", "e-content-path": "g7h8i9..." }这三个参数每次请求都会变化,且缺一不可。服务器会对这些参数进行严格校验,直接复制前一次的参数值会导致请求失败。
2.2 加密参数初步分析
通过对比多次请求,可以总结出三个参数的特点:
| 参数名称 | 长度 | 变化规律 | 疑似加密方式 |
|---|---|---|---|
| businessData | 变长 | 随请求内容变化 | SM4加密 |
| e-sign | 64字符 | 与businessData相关 | SM3哈希 |
| e-content-path | 88字符 | 独立变化 | SM2加密 |
3. businessData参数逆向
3.1 SM4加密识别
在开发者工具中搜索"businessData"关键词,可以定位到生成该参数的代码位置。通过分析发现:
- 参数值是通过一个名为"_"的函数生成
- 函数内部有明显的SM4加密特征
- 加密模式为CBC,使用PKCS7填充
- 需要key和iv两个关键参数
加密核心代码如下:
function _(t) { return "sm" === t.type ? sm4.encrypt( t.word, t.key, { mode: 'cbc', iv: t.iv, output: 'hex' } ) : /* 其他情况 */; }3.2 关键参数获取
要复现加密过程,需要获取以下三个要素:
- 明文(word):包含搜索条件的JSON字符串,如:
{"target":"SEARCH_RESULT_PAGE","query":"测试工程师"} - 密钥(key):通过E函数生成,涉及多个嵌套函数调用
- 初始向量(iv):生成逻辑与key类似
3.2.1 密钥生成逻辑
密钥生成主要经过以下步骤:
- 调用E函数,传入固定参数t
- 内部调用m函数生成随机数
- 使用w函数处理随机数
- 最终输出32字节的hex字符串
补全后的Node.js实现:
const crypto = require('crypto'); function getRandomValues(typedArray) { if (!(typedArray instanceof Uint8Array)) { throw new TypeError('参数必须是Uint8Array类型'); } const randomBuffer = crypto.randomBytes(typedArray.length); typedArray.set(randomBuffer); return typedArray; } function m(length) { const t = new Uint8Array(length); return getRandomValues(t); } function w(input) { // 实际网站中的特定处理逻辑 return Buffer.from(input).toString('hex'); } function E() { const t = m(16); const randomBytes = m(32); return w(randomBytes); }3.2.2 完整SM4加密实现
结合上述分析,完整的businessData生成代码如下:
const { sm4 } = require('sm-crypto'); function generateBusinessData(searchParams) { const plaintext = JSON.stringify({ target: "SEARCH_RESULT_PAGE", ...searchParams }); const key = E(); // 使用上述E函数生成 const iv = E(); // 生成逻辑与key相同 return sm4.encrypt(plaintext, key, { mode: 'cbc', iv: iv, output: 'hex' }); }4. e-sign参数逆向
4.1 SM3哈希识别
e-sign参数的生成相对简单,通过搜索可以发现:
- 由b函数生成,实际是SM3哈希算法
- 输入为固定字符串"business"加上businessData值再加上"data"
- 输出为64字符的hex字符串
关键代码位置:
var b = global.loader("gGBh").sm3; var o = b("business" + n + "data"); // n为businessData值4.2 Node.js实现
使用sm-crypto库的SM3实现:
const { sm3 } = require('sm-crypto'); function generateESign(businessData) { return sm3(`business${businessData}data`); }5. e-content-path参数逆向
5.1 SM2加密分析
这个参数是最复杂的,涉及以下技术点:
- 使用SM2非对称加密
- 需要处理Webpack模块加载
- 涉及多个临时参数的生成
- 需要特定的公钥进行加密
加密过程主要步骤:
- 生成两个32字节随机数x和C
- 使用SM2加密特定数据
- 拼接加密结果生成最终值
5.2 关键代码实现
补全后的Node.js代码:
const { sm2 } = require('sm-crypto'); // 网站使用的固定公钥 const PUBLIC_KEY = '043f4a9673db98fd52a87e087da75ca8d4978748188e29373acc131887d7b78ee89b07364f644352e4cb4029d8330509368b27b10638345c8afd41149626d917aa'; function generateEContentPath() { const x = m(32); // 随机数1 const C = m(32); // 随机数2 const dataToEncrypt = Buffer.concat([ Buffer.from(x), Buffer.from(C) ]).toString('hex'); // SM2加密 const encrypted = sm2.doEncrypt( dataToEncrypt, PUBLIC_KEY, { inputEncoding: 'hex', outputEncoding: 'base64' } ); return Buffer.from(encrypted, 'base64').toString('hex'); }6. 完整请求流程实现
6.1 Python实现示例
将上述分析结果整合为Python爬虫代码:
import requests from sm_crypto import sm2, sm3, sm4 def generate_business_data(search_params): # 实现SM4加密逻辑 pass def generate_e_sign(business_data): # 实现SM3哈希逻辑 pass def generate_e_content_path(): # 实现SM2加密逻辑 pass def fetch_job_data(keyword, page=1): business_data = generate_business_data({ "query": keyword, "page": page }) e_sign = generate_e_sign(business_data) e_content_path = generate_e_content_path() headers = { "User-Agent": "Mozilla/5.0", "Content-Type": "application/json" } payload = { "businessData": business_data, "e-sign": e_sign, "e-content-path": e_content_path } response = requests.post( "https://xxx.com/api/job/search", headers=headers, json=payload ) return response.json()6.2 注意事项
在实际使用中需要注意:
- 请求频率控制:即使破解了加密,也应遵守robots.txt和合理爬取间隔
- 参数时效性:加密参数可能有时间敏感性,不宜长时间缓存
- 错误处理:网站可能调整加密逻辑,需要完善的错误监控
- 法律合规:仅爬取公开数据,避免个人信息和商业机密
7. 常见问题与解决方案
7.1 加密结果不一致
可能原因及解决方法:
- 密钥生成差异:检查随机数生成逻辑是否完全一致
- 编码问题:确保所有中间步骤使用相同的编码方式
- 填充模式:确认使用PKCS7填充
- 加密模式:必须是CBC模式
7.2 Webpack模块加载问题
处理建议:
- 提取完整的Webpack加载器代码
- 在本地模拟模块加载环境
- 使用
global.loader替代原生的__webpack_require__
7.3 请求被拒绝
排查步骤:
- 验证三个加密参数是否都正确生成
- 检查请求头是否完整(特别是Content-Type)
- 确认User-Agent是常见的浏览器标识
- 尝试添加Referer等常见反爬头
8. 技术要点总结
通过这次逆向分析,我们可以总结出几个关键经验:
- 条件断点是调试动态加密代码的利器
- Webpack打包的代码可以通过模块ID定位具体函数
- 国密算法(SM2/SM3/SM4)在Web加密中应用越来越广泛
- 复合加密(对称+非对称+哈希)正在成为反爬标配
对于想要学习Web逆向的朋友,我的建议是:
- 从简单的网站开始,逐步提升难度
- 熟练掌握开发者工具的各种调试功能
- 建立自己的代码片段库,积累常见加密算法实现
- 关注WebAssembly等新技术的应用
最后提醒大家,技术研究要遵守法律法规,尊重网站的数据权益,将爬虫技术用在正当的用途上。