中检检通逆向分析文档.md 7.2 KB

中检检通(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

请求体示例:

{"rating_no":"519220343"}

2.2 关键请求头

请求头 示例值 说明
sign 0e576e116bec064c34fb28f15906d6b3 32位hex,动态生成的签名
datetime 2026-02-02 11:57:50 当前时间,与sign计算关联

2.3 响应结构

{
  "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 源码中找到以下常量定义:

// 模块 "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 请求拦截器)

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 解密:

// 解密函数
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 依赖

pip install requests pycryptodome

5.2 核心代码

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 时效性signdatetime 必须配对,且时间不能与服务器时间偏差过大,否则请求会被拒绝
  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 中可能显示为乱码,这是原始数据问题而非解密错误