# 中检检通(zhongjianjiantong.com)接口逆向分析 ## 一、目标 对 `https://www.zhongjianjiantong.com` 网站的 API 接口进行逆向分析,解决以下两个问题: 1. **请求签名**:`sign` 请求头的生成方式 2. **响应解密**:接口返回的加密数据(`data` 字段)的解密方式 --- ## 二、请求分析 ### 2.1 目标接口 ``` POST https://www.zhongjianjiantong.com/Api/OrderRatingGoods/webDetail Content-Type: application/json;charset=UTF-8 ``` 请求体示例: ```json {"rating_no":"519220343"} ``` ### 2.2 关键请求头 | 请求头 | 示例值 | 说明 | |--------|--------|------| | `sign` | `0e576e116bec064c34fb28f15906d6b3` | 32位hex,动态生成的签名 | | `datetime` | `2026-02-02 11:57:50` | 当前时间,与sign计算关联 | ### 2.3 响应结构 ```json { "code": 200, "msg": "", "data": "leUdchIlW5WHRTVdKEVqc0En...(Base64密文)", "iv": "894CA3EE5756EDF4" } ``` - `data`:Base64 编码的 AES 密文 - `iv`:16 字符的初始化向量(AES-CBC 模式使用) --- ## 三、逆向过程 ### 3.1 入口定位 **目标文件**:`https://www.zhongjianjiantong.com/web/static/js/index.e181df9a.js` 该文件为 webpack 打包的业务代码(单行压缩格式),通过以下方式定位关键逻辑: 1. 搜索 `webDetail` → 找到 API 接口定义位置 2. 搜索 `interceptors.response` → 找到 axios 响应拦截器 3. 搜索 `JSON.parse` → 找到解密函数 `decryptedData` 4. 搜索 `RESPONSE_KE` → 找到密钥常量定义 ### 3.2 常量定义模块(模块 `8fc7`) 在 JS 源码中找到以下常量定义: ```javascript // 模块 "8fc7" — 常量定义 var v = "3bd48ea5e910b195843941351be7cbae"; // RESPONSE_KE — 响应解密密钥 e.RESPONSE_KE = v; var g = "1ba48ea2e910b666843941351be7cbad"; // REQUESR_KEY — 请求签名密钥(原文拼写错误) e.REQUESR_KEY = g; var p = "sign"; // e.SIGN — sign请求头名 var h = "datetime"; // e.DATETIME — datetime请求头名 ``` ### 3.3 请求签名逻辑(axios 请求拦截器) ```javascript d.interceptors.request.use(function(t) { // 生成当前时间字符串 var o = moment(new Date().getTime()).format("YYYY-MM-DD HH:mm:ss"); // sign = MD5(REQUESR_KEY + 秒级时间戳) t.headers[r.SIGN] = md5(r.REQUESR_KEY + new Date(o).getTime() / 1e3); // datetime = 当前时间字符串 t.headers[r.DATETIME] = o; return t; }); ``` **签名算法**: ``` sign = MD5("1ba48ea2e910b666843941351be7cbad" + 秒级时间戳) ``` 其中秒级时间戳 = `new Date("YYYY-MM-DD HH:mm:ss").getTime() / 1000` ### 3.4 响应解密逻辑 在 axios 响应拦截器中,接收到响应后调用 `decryptedData` 解密: ```javascript // 解密函数 var r = function(t) { var e = t.encryptedData; // Base64密文(即 response.data) var n = t.iv_data; // IV(即 response.iv) // 密钥:UTF-8编码 RESPONSE_KE var r = CryptoJS.enc.Utf8.parse("3bd48ea5e910b195843941351be7cbae"); // IV:UTF-8编码 var o = CryptoJS.enc.Utf8.parse(n); // AES-CBC 解密 var s = CryptoJS.AES.decrypt( { ciphertext: CryptoJS.enc.Base64.parse(e) }, r, { iv: o, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 } ); var c = s.toString(CryptoJS.enc.Utf8); return c ? JSON.parse(c) : c; }; ``` --- ## 四、加密算法总结 ### 4.1 请求签名(sign) | 项目 | 值 | |------|-----| | 算法 | MD5 | | 输入 | `REQUEST_KEY` + 秒级时间戳(整数) | | REQUEST_KEY | `1ba48ea2e910b666843941351be7cbad` | | 输出 | 32位小写hex字符串 | ### 4.2 响应解密 | 项目 | 值 | |------|-----| | 算法 | AES | | 模式 | CBC | | 填充 | PKCS7 | | 密钥 | `3bd48ea5e910b195843941351be7cbae`(UTF-8编码,16字节) | | IV | 响应JSON中的 `iv` 字段(UTF-8编码,16字节) | | 密文 | 响应JSON中的 `data` 字段(Base64编码) | | 明文 | JSON字符串 | ### 4.3 加密流程图 ``` 请求方向: ┌──────────┐ sign = MD5(KEY + timestamp) ┌──────────┐ │ 客户端 │ ──────────────────────────────────→ │ 服务端 │ │ │ headers: { sign, datetime } │ │ └──────────┘ └──────────┘ 响应方向: ┌──────────┐ { data: Base64(AES_CBC(json)), iv: "..." } ┌──────────┐ │ 客户端 │ ←────────────────────────────────────────────── │ 服务端 │ │ │ AES.decrypt(data, RESPONSE_KEY, iv) │ │ └──────────┘ └──────────┘ ``` --- ## 五、Python 实现 ### 5.1 依赖 ```bash pip install requests pycryptodome ``` ### 5.2 核心代码 ```python import hashlib import json import base64 from datetime import datetime from Crypto.Cipher import AES from Crypto.Util.Padding import unpad RESPONSE_KEY = b"3bd48ea5e910b195843941351be7cbae" REQUEST_KEY = "1ba48ea2e910b666843941351be7cbad" def make_sign(): """生成sign和datetime请求头""" now = datetime.now() dt_str = now.strftime("%Y-%m-%d %H:%M:%S") timestamp = int(datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S").timestamp()) raw = REQUEST_KEY + str(timestamp) sign = hashlib.md5(raw.encode()).hexdigest() return sign, dt_str def decrypt_response(data_b64, iv_hex): """AES-CBC解密响应数据""" iv = iv_hex.encode("utf-8") cipher = AES.new(RESPONSE_KEY, AES.MODE_CBC, iv) ciphertext = base64.b64decode(data_b64) plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) return json.loads(plaintext.decode("utf-8")) ``` ### 5.3 JS 与 Python 对应关系 | JS(CryptoJS) | Python(pycryptodome) | |----------------|----------------------| | `CryptoJS.enc.Utf8.parse(key)` | `key.encode("utf-8")` 或直接 `b"..."` | | `CryptoJS.enc.Base64.parse(data)` | `base64.b64decode(data)` | | `CryptoJS.AES.decrypt(...)` | `AES.new(key, AES.MODE_CBC, iv).decrypt(...)` | | `CryptoJS.pad.Pkcs7` | `unpad(data, AES.block_size)` | | `CryptoJS.MD5(str).toString()` | `hashlib.md5(str.encode()).hexdigest()` | --- ## 六、注意事项 1. **sign 时效性**:`sign` 与 `datetime` 必须配对,且时间不能与服务器时间偏差过大,否则请求会被拒绝 2. **IV 编码方式**:IV 是以 UTF-8 字符串形式使用的(不是 hex 解码),例如 `"894CA3EE5756EDF4"` 就是 16 个 ASCII 字符 = 16 字节 3. **密钥长度**:`RESPONSE_KEY` 为 32 个 hex 字符,但作为 UTF-8 字符串使用时是 32 字节,实际上 CryptoJS 默认会将超过 16 字节的密钥用于 AES-256;此处密钥恰好为 32 ASCII 字符 = 32 字节,对应 AES-256 4. **部分字段编码**:某些商品名称字段(如 `goods_uni_str`)包含非 UTF-8 编码的字符,在 Python 中可能显示为乱码,这是原始数据问题而非解密错误