Files
hello_js_reverse_skill/references/crypto-patterns.md
T
白夜 b5cf8b121a feat(v2.4.0): JSVMP 双路径攻克体系 + jsdom 环境补丁知识库
基于实战经验重构 JSVMP 分析框架:

- SKILL.md: 新增路径选择决策树(路径 A 算法追踪 vs 路径 B 环境伪装)
- SKILL.md: 路径 B「环境伪装六步法」完整方法论(采集→对比→分级→补丁→验证→端到端)
- SKILL.md: 新增场景 10「JSVMP + jsdom 环境伪装」、解法模式 E
- SKILL.md: 标注三板斧适用边界与局限,经验法则扩充至 22 条
- references/jsdom-env-patches.md: 新增 jsdom 环境补丁知识库(Function.toString 三层防御 / plugins 完整模拟 / DOM 布局属性 / 30+ API 存根)
- cases/: 新增经验库系统(模板 + 首个 JSVMP 实战案例含 58 项环境检测差异全表)
- references/jsvmp-analysis.md: JSVMP 专项分析详细指南
- references/troubleshooting.md: 常见问题排查指南
- templates/python-request/: Python 项目模板
- 各 references 文档增强、Python 技术栈支持、MCP v1.1 适配(44→52 工具)

Made-with: Cursor
2026-04-02 18:10:05 +08:00

12 KiB
Raw Blame History

常见加密模式识别与还原(Node.js / Python 双语言)

快速识别表

特征 可能的算法 验证方法
32位十六进制 MD5 用已知输入验证
40位十六进制 SHA-1 同上
64位十六进制 SHA-256 同上
128位十六进制 SHA-512 同上
= 结尾,含 A-Za-z0-9+/ Base64 直接 atob 解码
= 结尾,含 A-Za-z0-9-_ Base64url 替换 -_ 为 +/ 后解码
固定长度密文,16字节倍数 AES (128-bit key) 寻找 key/iv
固定长度密文,8字节倍数 DES / 3DES 寻找 8字节 key
超长数字字符串 RSA 寻找公钥
CryptoJS 关键词 CryptoJS 库 直接搜索源码

1. MD5

标准 MD5

// Node.js
const crypto = require('crypto');
function md5(str) {
    return crypto.createHash('md5').update(str).digest('hex');
}
# Python
import hashlib

def md5(text: str) -> str:
    return hashlib.md5(text.encode('utf-8')).hexdigest()

自定义 MD5(非标准实现)

某些网站修改了 MD5 的内部参数(如 chrsz=16),导致输出与标准 MD5 不同。

识别方法:用相同输入对比标准 MD5 输出,如果不一致则为自定义实现。

还原策略:必须提取原始 JS 实现,在 Node.js 中直接执行。

常见 MD5 签名模式

// 模式1:简单拼接
sign = md5(param1 + param2 + timestamp + secret)

// 模式2:排序拼接
params = Object.keys(data).sort().map(k => k + '=' + data[k]).join('&')
sign = md5(params + secret)

// 模式3:嵌套哈希
sign = md5(md5(password) + timestamp)

2. HMAC

// Node.js
const crypto = require('crypto');
function hmacSha256(message, secret) {
    return crypto.createHmac('sha256', secret).update(message).digest('hex');
}
# Python
import hmac
import hashlib

def hmac_sha256(message: str, secret: str) -> str:
    return hmac.new(secret.encode('utf-8'), message.encode('utf-8'), hashlib.sha256).hexdigest()

识别特征:搜索 HMAC、createHmac、CryptoJS.HmacSHA256

3. AES

AES-CBC

// Node.js
const crypto = require('crypto');

function aesEncrypt(plaintext, key, iv) {
    const cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
    cipher.setAutoPadding(true); // PKCS7
    let encrypted = cipher.update(plaintext, 'utf8', 'base64');
    encrypted += cipher.final('base64');
    return encrypted;
}

function aesDecrypt(ciphertext, key, iv) {
    const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
    decipher.setAutoPadding(true);
    let decrypted = decipher.update(ciphertext, 'base64', 'utf8');
    decrypted += decipher.final('utf8');
    return decrypted;
}
# Python
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

def aes_cbc_encrypt(plaintext: str, key: str, iv: str) -> str:
    cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
    padded = pad(plaintext.encode('utf-8'), AES.block_size)
    return base64.b64encode(cipher.encrypt(padded)).decode('utf-8')

def aes_cbc_decrypt(ciphertext_b64: str, key: str, iv: str) -> str:
    cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
    decrypted = cipher.decrypt(base64.b64decode(ciphertext_b64))
    return unpad(decrypted, AES.block_size).decode('utf-8')

AES-ECB

// Node.js
function aesEcbEncrypt(plaintext, key) {
    const cipher = crypto.createCipheriv('aes-128-ecb', key, null);
    cipher.setAutoPadding(true);
    let encrypted = cipher.update(plaintext, 'utf8', 'base64');
    encrypted += cipher.final('base64');
    return encrypted;
}
# Python
def aes_ecb_encrypt(plaintext: str, key: str) -> str:
    cipher = AES.new(key.encode('utf-8'), AES.MODE_ECB)
    padded = pad(plaintext.encode('utf-8'), AES.block_size)
    return base64.b64encode(cipher.encrypt(padded)).decode('utf-8')

CryptoJS 兼容

const CryptoJS = require('crypto-js');

// CryptoJS 默认使用 PKCS7 填充
const encrypted = CryptoJS.AES.encrypt(plaintext, CryptoJS.enc.Utf8.parse(key), {
    iv: CryptoJS.enc.Utf8.parse(iv),
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
}).toString();

关键参数识别:

  • key 长度:16字节=AES-128,24字节=AES-192,32字节=AES-256
  • iv 长度:固定16字节
  • 模式:CBC(需要iv)、ECB(不需要iv)、CTR、GCM
  • 填充:PKCS7(最常见)、ZeroPadding、NoPadding
  • 输出格式:Base64(最常见)、Hex

4. DES / 3DES

// Node.js
function desEncrypt(plaintext, key) {
    const cipher = crypto.createCipheriv('des-ecb', key, null); // 8字节key
    cipher.setAutoPadding(true);
    let encrypted = cipher.update(plaintext, 'utf8', 'base64');
    encrypted += cipher.final('base64');
    return encrypted;
}

function tripleDesEncrypt(plaintext, key, iv) {
    const cipher = crypto.createCipheriv('des-ede3-cbc', key, iv); // 24字节key
    cipher.setAutoPadding(true);
    let encrypted = cipher.update(plaintext, 'utf8', 'base64');
    encrypted += cipher.final('base64');
    return encrypted;
}
# Python
from Crypto.Cipher import DES, DES3
from Crypto.Util.Padding import pad

def des_ecb_encrypt(plaintext: str, key: str) -> str:
    cipher = DES.new(key.encode('utf-8'), DES.MODE_ECB)
    padded = pad(plaintext.encode('utf-8'), DES.block_size)
    return base64.b64encode(cipher.encrypt(padded)).decode('utf-8')

def triple_des_cbc_encrypt(plaintext: str, key: str, iv: str) -> str:
    cipher = DES3.new(key.encode('utf-8'), DES3.MODE_CBC, iv.encode('utf-8'))
    padded = pad(plaintext.encode('utf-8'), DES3.block_size)
    return base64.b64encode(cipher.encrypt(padded)).decode('utf-8')

5. RSA

// Node.js
const crypto = require('crypto');

function rsaEncrypt(plaintext, publicKey) {
    const buffer = Buffer.from(plaintext, 'utf8');
    const encrypted = crypto.publicEncrypt({
        key: publicKey,
        padding: crypto.constants.RSA_PKCS1_PADDING
    }, buffer);
    return encrypted.toString('base64');
}

使用 node-forge(更灵活):

const forge = require('node-forge');

function rsaEncrypt(plaintext, publicKeyPem) {
    const publicKey = forge.pki.publicKeyFromPem(publicKeyPem);
    const encrypted = publicKey.encrypt(plaintext, 'RSAES-PKCS1-V1_5');
    return forge.util.encode64(encrypted);
}
# Python
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5

def rsa_encrypt(plaintext: str, public_key_pem: str) -> str:
    key = RSA.import_key(public_key_pem)
    cipher = PKCS1_v1_5.new(key)
    encrypted = cipher.encrypt(plaintext.encode('utf-8'))
    return base64.b64encode(encrypted).decode('utf-8')

# OAEP 模式
from Crypto.Cipher import PKCS1_OAEP

def rsa_encrypt_oaep(plaintext: str, public_key_pem: str) -> str:
    key = RSA.import_key(public_key_pem)
    cipher = PKCS1_OAEP.new(key)
    encrypted = cipher.encrypt(plaintext.encode('utf-8'))
    return base64.b64encode(encrypted).decode('utf-8')

常见 RSA 公钥格式:

  • PEM 格式(-----BEGIN PUBLIC KEY-----)
  • 模数(n) + 指数(e) 格式(需要手动构造 PEM)

6. Base64 变体

// Node.js
const encoded = Buffer.from(str).toString('base64');
const decoded = Buffer.from(encoded, 'base64').toString();

// Base64url(URL安全变体)
function base64url(str) {
    return Buffer.from(str).toString('base64')
        .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}

// 自定义字符表 Base64
function customBase64(str, table) {
    const std = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    const b64 = Buffer.from(str).toString('base64');
    return b64.split('').map(c => {
        const idx = std.indexOf(c);
        return idx >= 0 ? table[idx] : c;
    }).join('');
}
# Python
import base64

encoded = base64.b64encode(text.encode('utf-8')).decode('utf-8')
decoded = base64.b64decode(encoded).decode('utf-8')

# Base64url(URL安全变体)
encoded_url = base64.urlsafe_b64encode(text.encode('utf-8')).decode('utf-8').rstrip('=')

# 自定义字符表 Base64
def custom_base64(text: str, table: str) -> str:
    std = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    b64 = base64.b64encode(text.encode('utf-8')).decode('utf-8')
    trans = str.maketrans(std, table)
    return b64.translate(trans)

7. 异或加密(XOR)

// Node.js
function xorEncrypt(plaintext, key) {
    const result = [];
    for (let i = 0; i < plaintext.length; i++) {
        result.push(plaintext.charCodeAt(i) ^ key.charCodeAt(i % key.length));
    }
    return Buffer.from(result).toString('hex');
}
# Python
def xor_encrypt(plaintext: str, key: str) -> str:
    result = bytes([
        ord(plaintext[i]) ^ ord(key[i % len(key)])
        for i in range(len(plaintext))
    ])
    return result.hex()

8. RC4

// Node.js
function rc4(data, key) {
    const s = Array.from({ length: 256 }, (_, i) => i);
    let j = 0;
    for (let i = 0; i < 256; i++) {
        j = (j + s[i] + key.charCodeAt(i % key.length)) % 256;
        [s[i], s[j]] = [s[j], s[i]];
    }
    let i = 0; j = 0;
    const result = [];
    for (let k = 0; k < data.length; k++) {
        i = (i + 1) % 256;
        j = (j + s[i]) % 256;
        [s[i], s[j]] = [s[j], s[i]];
        result.push(data.charCodeAt(k) ^ s[(s[i] + s[j]) % 256]);
    }
    return Buffer.from(result);
}
# Python(使用 pycryptodome)
from Crypto.Cipher import ARC4

def rc4_encrypt(data: str, key: str) -> bytes:
    cipher = ARC4.new(key.encode('utf-8'))
    return cipher.encrypt(data.encode('utf-8'))

# 或手动实现(与 JS 版本逻辑一致)
def rc4_manual(data: str, key: str) -> bytes:
    s = list(range(256))
    j = 0
    for i in range(256):
        j = (j + s[i] + ord(key[i % len(key)])) % 256
        s[i], s[j] = s[j], s[i]
    i = j = 0
    result = []
    for char in data:
        i = (i + 1) % 256
        j = (j + s[i]) % 256
        s[i], s[j] = s[j], s[i]
        result.append(ord(char) ^ s[(s[i] + s[j]) % 256])
    return bytes(result)

9. 时间戳处理

// Node.js
const tsMs = Date.now();                      // 毫秒级(13位)
const tsSec = Math.floor(Date.now() / 1000);  // 秒级(10位)
# Python
import time

ts_ms = int(time.time() * 1000)  # 毫秒级(13位)
ts_sec = int(time.time())        # 秒级(10位)

注意:某些网站使用服务端时间戳,需要从响应头或接口获取。

10. 参数签名常见拼接模式

// Node.js
// 模式1:固定格式
sign = md5(`page=${page}&t=${timestamp}&key=${secret}`)

// 模式2:所有参数排序拼接
const params = { page: 1, size: 10, t: Date.now() };
const str = Object.keys(params).sort().map(k => `${k}=${params[k]}`).join('&');
sign = md5(str + secret)

// 模式3:JSON 字符串
sign = md5(JSON.stringify(data) + secret)

// 模式4:管道分隔
sign = md5(`${page}|${timestamp}|${secret}`)
# Python
import hashlib, json, time

def md5(text: str) -> str:
    return hashlib.md5(text.encode('utf-8')).hexdigest()

# 模式1:固定格式
sign = md5(f"page={page}&t={timestamp}&key={secret}")

# 模式2:所有参数排序拼接
params = {"page": 1, "size": 10, "t": int(time.time())}
param_str = "&".join(f"{k}={params[k]}" for k in sorted(params))
sign = md5(param_str + secret)

# 模式3:JSON 字符串(注意 separators 控制格式)
sign = md5(json.dumps(data, separators=(',', ':'), sort_keys=True) + secret)

# 模式4:管道分隔
sign = md5(f"{page}|{timestamp}|{secret}")