声明:本篇文章仅用于知识分享
实战网址:烯牛数据 · 新一代科创数据引擎
请求参数加密
- 访问网址,往下翻翻,可以看到触发了如下的数据包,请求参数进行了加密。
- 全局搜索
list_industries_by_sort
地址,有四处,都位于同一个文件中。
随便点一个看看,可以看到有payload
关键字。
打断点,刷新界面触发断点,一步一步往下调试,可以看到payload
传给了j.a.fetch()
函数。 - 定位
j.a.fetch()
函数。
打断点,跳转进来。可以看到明显给payload
和sig
赋值的语句。
最关键的就是如下这行代码。
var f = Object(u.c)(Object(u.d)(JSON.stringify(s.payload))), p = Object(u.e)(f);
涉及到一个变量和三个函数,一个一个看。
(1)变量s.payload
:直接输出看即可。
(2)函数Object(u.c)
:控制台输出,定位。
(3)函数Object(u.d)
:控制台输出,定位。
(4)函数Object(u.e)
:控制台输出,定位。
上面三个函数的代码可以不用看,直接调用js代码就可以了。
4. 目前的js代码如下。
// 定义请求的 payload
const s = {
"payload": {
"sort": 1,
"start": 20,
"limit": 20
}
};
// Base64 编码表
const _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
// 加密密钥
const _p = "your_secret_key"; // 需要替换为实际密钥
/**
* Base64 编码函数
* @param {string} e - 需要进行 Base64 编码的字符串
* @returns {string} - Base64 编码后的字符串
*/
function base64Encode(e) {
if (e == null) return null;
let c = "";
let t, n, r, o, i, a, u;
let l = 0;
while (l < e.length) {
o = (t = e.charCodeAt(l++)) >> 2;
i = ((3 & t) << 4) | ((n = e.charCodeAt(l++)) >> 4);
a = ((15 & n) << 2) | ((r = e.charCodeAt(l++)) >> 6);
u = 63 & r;
if (isNaN(n)) {
a = u = 64;
} else if (isNaN(r)) {
u = 64;
}
c += _keyStr.charAt(o) + _keyStr.charAt(i) + _keyStr.charAt(a) + _keyStr.charAt(u);
}
return c;
}
/**
* 异或加密函数
* @param {string} e - 需要加密的字符串
* @returns {string} - 加密后的字符串
*/
function xorEncrypt(e) {
if ((e = _u_e(e)) == null) return null;
let t = "";
for (let n = 0; n < e.length; n++) {
let r = _p.charCodeAt(n % _p.length);
t += String.fromCharCode(e.charCodeAt(n) ^ r);
}
return t;
}
/**
* 计算签名
* @param {string} e - 需要计算 MD5 的字符串
* @returns {string} - 计算后的 MD5 并转换为大写
*/
function generateSignature(e) {
return md5(e + _p).toUpperCase();
}
// 执行加密流程
const payloadString = JSON.stringify(s.payload); // 转换为 JSON 字符串
const encryptedData = base64Encode(xorEncrypt(payloadString)); // 先 XOR 加密,再 Base64 编码
const signature = generateSignature(encryptedData); // 计算签名
// 输出加密后的数据和签名
console.log("加密后的数据:", encryptedData);
console.log("签名:", signature);
运行提示_u_e is not defined
,去找一下补到代码中。
运行提示_p is not defined
,去找_p
。
运行提示md5 is not defined
,这里需要导入crypto-js
库,可以通过npm install crypto-js
命令下载。
const CryptoJS = require("crypto-js");
/**
* 计算字符串的 MD5 签名
* @param {string} data - 需要计算签名的字符串
* @param {string} secretKey - 用于加密的密钥
* @returns {string} - 计算出的 MD5 签名(大写)
*/
function generateSignature(data, secretKey) {
if (!data || !secretKey) {
throw new Error("数据和密钥不能为空!");
}
return CryptoJS.MD5(data + secretKey).toString().toUpperCase();
}
// 示例调用
const _p = "your_secret_key"; // 请替换为实际密钥
const testData = "example_data";
const signature = generateSignature(testData, _p);
console.log("签名:", signature);
运行提示CryptoJS.MD5(...).toUpperCase is not a function
,是因为在转换为大写之前需要先用toString
函数转为字符串。
完整代码如下:
// query_parameter_encrypt.js
const CryptoJS = require("crypto-js");
// 请求参数
const s = {
"payload": {
"sort": 1,
"start": 0,
"limit": 20
}
};
// Base64 编码表
const _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
// 加密密钥
const _p = "W5D80NFZHAYB8EUI2T649RT2MNRMVE2O";
/**
* 处理字符串的 Unicode 转换
* @param {string} e - 需要转换的字符串
* @returns {string} - 转换后的字符串
*/
function unicodeEncode(e) {
if (e == null) return null;
e = e.replace(/\r\n/g, "\n");
let t = "";
for (let n = 0; n < e.length; n++) {
let r = e.charCodeAt(n);
if (r < 128) {
t += String.fromCharCode(r);
} else if (r > 127 && r < 2048) {
t += String.fromCharCode((r >> 6) | 192);
t += String.fromCharCode((r & 63) | 128);
} else {
t += String.fromCharCode((r >> 12) | 224);
t += String.fromCharCode(((r >> 6) & 63) | 128);
t += String.fromCharCode((r & 63) | 128);
}
}
return t;
}
/**
* Base64 编码
* @param {string} e - 需要进行 Base64 编码的字符串
* @returns {string} - Base64 编码后的字符串
*/
function base64Encode(e) {
if (e == null) return null;
let c = "";
let t, n, r, o, i, a, u;
let l = 0;
while (l < e.length) {
o = (t = e.charCodeAt(l++)) >> 2;
i = ((t & 3) << 4) | ((n = e.charCodeAt(l++)) >> 4);
a = ((n & 15) << 2) | ((r = e.charCodeAt(l++)) >> 6);
u = r & 63;
if (isNaN(n)) {
a = u = 64;
} else if (isNaN(r)) {
u = 64;
}
c += _keyStr.charAt(o) + _keyStr.charAt(i) + _keyStr.charAt(a) + _keyStr.charAt(u);
}
return c;
}
/**
* XOR 加密
* @param {string} e - 需要加密的字符串
* @returns {string} - 加密后的字符串
*/
function xorEncrypt(e) {
if ((e = unicodeEncode(e)) == null) return null;
let t = "";
for (let n = 0; n < e.length; n++) {
let r = _p.charCodeAt(n % _p.length);
t += String.fromCharCode(e.charCodeAt(n) ^ r);
}
return t;
}
/**
* 计算签名
* @param {string} e - 需要计算 MD5 的字符串
* @returns {string} - 计算后的 MD5 并转换为大写
*/
function generateSignature(e) {
return CryptoJS.MD5(e + _p).toString().toUpperCase();
}
/**
* 生成加密后的 payload 和签名
* @param {Object} s - 需要加密的对象
* @returns {Array} - [加密后的 payload, 签名]
*/
function generate(s) {
const payload = base64Encode(xorEncrypt(JSON.stringify(s.payload)));
const sign = generateSignature(payload);
return [payload, sign];
}
// 输出加密后的数据和签名
console.log(generate(s));
运行结果如下。
成功,接下来就是编写python代码获取数据了。
import requests
import json
import execjs
# 目标请求的 URL
URL = "https://www.xiniudata.com/api2/service/x_service/person_industry_list/list_industries_by_sort"
# 请求头(Content-Type 必须设置,否则无法获取数据)
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
"Content-Type": "application/json"
}
# 请求的参数
REQUEST_PAYLOAD = {
"payload": {
"sort": 1,
"start": 0,
"limit": 20
}
}
# 读取 JavaScript 代码并编译
with open("query_parameter_encrypt.js", mode="r", encoding="utf-8") as file:
js_code = file.read()
js_context = execjs.compile(js_code)
# 生成加密的 payload 和签名
payload, sig = js_context.call("generate", REQUEST_PAYLOAD)
# 组织最终请求数据
DATA = {
"payload": payload,
"sig": sig,
"v": 1
}
# 发送 POST 请求
response = requests.post(URL, headers=HEADERS, data=json.dumps(DATA))
# 解析并打印返回的 JSON 数据
try:
response_data = response.json()
print(json.dumps(response_data, indent=4, ensure_ascii=False))
except json.JSONDecodeError:
print("返回数据格式错误:", response.text)
运行结果如下。
返回数据解密
- 一般来说对于返回数据的解密,常规操作是搜索
interceptors
,但是很可惜不适用于该网站。那就得想其他的办法。 - 我们知道该数据包采用的是JSON格式数据,解密出来后肯定需要进行JSON解析,所以可以搜索
JSON.parse
关键字。
地方比较多,需要进行筛选。如果JSON.parse
是该函数的第一行代码就铁定不是,因为是需要先解密再进行JSON解析的。点击几个,发现了一处跟加密逻辑很像的代码。(其实一般情况下加密逻辑和解密逻辑是写在一起的) - 打断点,一行一行调试。
可以看到s
的值就是返回的数据,说明我们没找错地方。y
的值是明文了,所以解密逻辑肯定跟Object(u.a)
和Object(u.b)
相关。
找到这两个函数的定义。 - 接下来就跟加密逻辑一样了,把相关的js代码复制出来,把需要用到的变量和参数都补齐。
// response_data_decrypt.js
// Base64 编码表
const _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
// 解密密钥
const _p = "W5D80NFZHAYB8EUI2T649RT2MNRMVE2O";
/**
* Base64 解码
* @param {string} e - 需要解码的 Base64 字符串
* @returns {string} - 解码后的字符串
*/
function base64Decode(e) {
let t, n, r, o, i, a, u = "";
let c = 0;
e = e.replace(/[^A-Za-z0-9\+\/\=]/g, ""); // 过滤无效字符
while (c < e.length) {
t = (_keyStr.indexOf(e.charAt(c++)) << 2) | ((o = _keyStr.indexOf(e.charAt(c++))) >> 4);
n = ((o & 15) << 4) | ((i = _keyStr.indexOf(e.charAt(c++))) >> 2);
r = ((i & 3) << 6) | (a = _keyStr.indexOf(e.charAt(c++)));
u += String.fromCharCode(t);
if (i !== 64) u += String.fromCharCode(n);
if (a !== 64) u += String.fromCharCode(r);
}
return u;
}
/**
* 处理 Unicode 解码
* @param {string} e - 需要转换的字符串
* @returns {string} - 解析后的 Unicode 字符串
*/
function unicodeDecode(e) {
let t = "", n = 0, r, o, i;
while (n < e.length) {
r = e.charCodeAt(n);
if (r < 128) {
t += String.fromCharCode(r);
n++;
} else if (r > 191 && r < 224) {
o = e.charCodeAt(n + 1);
t += String.fromCharCode(((r & 31) << 6) | (o & 63));
n += 2;
} else {
o = e.charCodeAt(n + 1);
i = e.charCodeAt(n + 2);
t += String.fromCharCode(((r & 15) << 12) | ((o & 63) << 6) | (i & 63));
n += 3;
}
}
return t;
}
/**
* XOR 解密
* @param {string} e - 需要解密的字符串
* @returns {string} - 解密后的字符串
*/
function xorDecrypt(e) {
let t = "";
for (let n = 0; n < e.length; n++) {
let r = _p.charCodeAt(n % _p.length);
t += String.fromCharCode(e.charCodeAt(n) ^ r);
}
return unicodeDecode(t);
}
/**
* 解密主函数
* @param {string} e - 需要解密的 Base64 加密数据
* @returns {string} - 解密后的数据
*/
function decrypt(e) {
return xorDecrypt(base64Decode(e));
}
// 示例调用
const encryptedData = "返回的加密数据"; // 需要替换为实际加密数据
console.log(decrypt(encryptedData));
运行结果如下。
成功。
5. 写python代码,将加密逻辑和解密逻辑进行融合。
import json
import requests
import execjs
import subprocess
from functools import partial
# 设置 subprocess 默认编码为 UTF-8
subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")
# 目标 URL
URL = "https://www.xiniudata.com/api2/service/x_service/person_industry_list/list_industries_by_sort"
# 请求头(Content-Type 必须设置,否则无法获取数据)
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
),
"Content-Type": "application/json"
}
# 需要加密的请求参数
REQUEST_PAYLOAD = {
"payload": {
"sort": 1,
"start": 0,
"limit": 20
}
}
def load_js_file(filename):
"""
读取 JavaScript 文件内容并编译
:param filename: JavaScript 文件路径
:return: 编译后的 execjs 上下文
"""
with open(filename, mode="r", encoding="utf-8") as file:
js_code = file.read()
return execjs.compile(js_code)
# 1. 加密请求参数
encrypt_js = load_js_file("query_parameter_encrypt.js")
payload, sig = encrypt_js.call("generate", REQUEST_PAYLOAD)
# 2. 组织请求数据
request_data = {
"payload": payload,
"sig": sig,
"v": 1
}
# 3. 发送 POST 请求
response = requests.post(URL, headers=HEADERS, data=json.dumps(request_data))
# 4. 解析返回的数据
try:
response_json = response.json()
encrypted_response_data = response_json.get('d', '')
# 5. 解密返回数据
decrypt_js = load_js_file("response_data_decrypt.js")
decrypted_data = decrypt_js.call("decrypt", encrypted_response_data)
# 6. 输出解密后的数据
print(decrypted_data)
except json.JSONDecodeError:
print("返回数据格式错误:", response.text)
运行结果如下。
跟页面上显示的对应。
大功告成。