在网络爬虫开发中,当面对带有签名验证的接口时,参数签名算法逆向是绕开反爬机制的核心步骤。网站通过 MD5、AES、RSA、SM2 等加密算法对请求参数进行签名,验证请求的合法性,防止恶意爬取。本文将从签名算法的核心原理出发,结合实战案例,系统讲解四大主流签名算法的逆向思路、工具使用和代码实现,帮助开发者快速突破签名验证壁垒。
一、签名算法逆向核心逻辑
1. 签名的本质与作用
请求参数签名是网站后端对前端请求的 "身份校验" 机制:前端将关键参数按固定规则排序、拼接后,通过加密算法生成唯一签名串(如 sign 参数),后端接收请求后执行相同逻辑计算签名,对比一致则认为请求合法。其核心作用包括:
- 防止参数被篡改(如价格、页码等关键信息);
- 验证请求来源(仅合法前端能生成正确签名);
- 抵御重放攻击(部分签名包含时间戳、随机数)。
2. 签名算法逆向通用流程
无论哪种加密算法,逆向过程均可遵循以下五步流程:
- 定位签名参数:通过抓包工具(Fiddler、Charles、Chrome 开发者工具)分析请求,找到签名字段(常见名称:sign、signature、token);
- 追踪参数生成逻辑 :在前端源码(JS 为主)中搜索签名字段,通过断点调试、关键词检索(如
md5、encrypt、sign)定位生成签名的核心函数; - 解析算法细节:分析核心函数的参数处理规则(排序、拼接、加盐)、加密算法类型、密钥来源(硬编码、接口返回、本地存储);
- 模拟实现:用 Python 复现参数处理逻辑和加密算法,生成与前端一致的签名;
- 验证调试:将生成的签名代入请求,对比响应结果,排查参数处理、算法实现中的问题。
二、四大签名算法逆向实战(含 Python 实现)
(一)MD5 签名:最常见的轻量验证
1. 算法特性
- 哈希算法(不可逆),输出 32 位(小写 / 大写)或 16 位(取中间 16 位)字符串;
- 核心逻辑:参数排序 → 拼接字符串 → 加盐(salt)→ MD5 加密;
- 典型场景:普通接口的参数校验(如电商商品列表、新闻资讯接口)。
2. 逆向步骤与案例
步骤 1:抓包分析请求
以某资讯接口为例,抓包得到请求参数:
plaintext
GET /api/news?page=1&limit=10×tamp=1699999999&sign=e10adc3949ba59abbe56e057f20f883e
- 关键参数:
page(页码)、limit(条数)、timestamp(时间戳)、sign(签名)。
步骤 2:定位签名生成函数
在前端 JS 源码中搜索 sign,找到核心函数:
javascript
运行
function generateSign(params) {
// 1. 按参数名 ASCII 升序排序
const sortedKeys = Object.keys(params).sort();
// 2. 拼接参数(key=value 形式)
let signStr = '';
sortedKeys.forEach(key => {
if (params[key] !== '') signStr += `${key}=${params[key]}&`;
});
// 3. 加盐(固定 salt:abc123)
signStr += 'salt=abc123';
// 4. MD5 加密(32 位小写)
return md5(signStr);
}
步骤 3:Python 模拟实现
python
运行
import hashlib
import time
def generate_md5_sign(page=1, limit=10):
# 1. 构造基础参数
params = {
'page': page,
'limit': limit,
'timestamp': int(time.time()) # 与前端一致的时间戳
}
# 2. 按参数名升序排序
sorted_keys = sorted(params.keys())
# 3. 拼接字符串(key=value&key=value)
sign_str = ''
for key in sorted_keys:
sign_str += f"{key}={params[key]}&"
# 4. 加盐(与前端一致的 salt)
sign_str += 'salt=abc123'
# 5. MD5 加密
md5 = hashlib.md5()
md5.update(sign_str.encode('utf-8'))
params['sign'] = md5.hexdigest() # 32 位小写
return params
# 测试:生成请求参数
request_params = generate_md5_sign(page=1, limit=10)
print(request_params)
# 输出:{'page': 1, 'limit': 10, 'timestamp': 1699999999, 'sign': 'e10adc3949ba59abbe56e057f20f883e'}
3. 常见变体与注意事项
- 加盐方式:固定盐(硬编码)、动态盐(接口返回、时间戳截取);
- 字符串拼接:是否包含空值参数、是否加固定前缀 / 后缀;
- 加密结果:16 位签名需取
md5.hexdigest()[8:-8]。
(二)AES 签名:对称加密的参数混淆
1. 算法特性
- 对称加密算法(需密钥和解密算法),常见模式:ECB、CBC(需 IV 向量);
- 填充方式:PKCS7(默认)、PKCS5;
- 核心逻辑:参数 JSON 序列化 → AES 加密 → Base64 编码(可选);
- 典型场景:敏感参数加密(如用户手机号、支付信息)、整体参数签名。
2. 逆向步骤与案例
步骤 1:抓包分析请求
某用户登录接口,请求体为:
json
{
"data": "U2FsdGVkX1+Z8sD4F4a9xQ===",
"sign": "a7b3d8f9e2c4b1a0f3e5d7c9b2a1f0e3"
}
data为 AES 加密后的用户信息(手机号 + 密码),sign为 AES 加密后的签名。
步骤 2:定位加密函数
搜索 AES、encrypt,找到核心函数:
javascript
运行
// 依赖 CryptoJS 库
const AES_KEY = '1234567890abcdef'; // 密钥(16 位)
const AES_IV = 'abcdef1234567890'; // IV 向量(CBC 模式需 16 位)
function aesEncrypt(data) {
const jsonStr = JSON.stringify(data);
const key = CryptoJS.enc.Utf8.parse(AES_KEY);
const iv = CryptoJS.enc.Utf8.parse(AES_IV);
// AES-CBC 模式,PKCS7 填充
const encrypted = CryptoJS.AES.encrypt(
jsonStr,
key,
{
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
);
// 输出 Base64 编码后的字符串
return encrypted.toString();
}
// 生成 sign:对参数 data 进行 AES 加密
function generateAesSign(userInfo) {
return aesEncrypt(userInfo);
}
步骤 3:Python 模拟实现
需安装依赖:pip install pycryptodome
python
运行
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64
import json
# 配置(与前端一致)
AES_KEY = b'1234567890abcdef' # 16 位密钥
AES_IV = b'abcdef1234567890' # 16 位 IV 向量
def aes_encrypt(data):
# 1. JSON 序列化
json_str = json.dumps(data, ensure_ascii=False)
# 2. 编码(UTF-8)
data_bytes = json_str.encode('utf-8')
# 3. AES-CBC 加密(PKCS7 填充)
cipher = AES.new(AES_KEY, AES.MODE_CBC, AES_IV)
encrypted_bytes = cipher.encrypt(pad(data_bytes, AES.block_size, style='pkcs7'))
# 4. Base64 编码
return base64.b64encode(encrypted_bytes).decode('utf-8')
# 测试:加密用户信息生成 sign
user_info = {'phone': '13800138000', 'password': '123456'}
sign = aes_encrypt(user_info)
print(sign)
# 输出:U2FsdGVkX1+Z8sD4F4a9xQ==="(与前端加密结果一致)
3. 常见变体与注意事项
- 密钥来源:硬编码(前端源码搜索)、接口返回(需先爬取密钥接口)、本地存储(LocalStorage、Cookie);
- 模式差异:ECB 模式无需 IV 向量,CBC 模式必须指定 IV;
- 编码方式:加密后可能用 Base64 或 Hex 编码,需与前端一致。
(三)RSA 签名:非对称加密的身份认证
1. 算法特性
- 非对称加密(公钥加密、私钥解密),常见密钥长度 1024/2048 位;
- 核心逻辑:参数拼接 → 私钥签名(前端)→ 公钥验证(后端);
- 典型场景:登录认证、支付接口、高安全性接口(如金融、政务系统)。
2. 逆向步骤与案例
步骤 1:抓包分析请求
某支付接口,请求参数中 sign 为 RSA 签名:
plaintext
POST /api/pay
{
"orderId": "ORD12345678",
"amount": 99.9,
"timestamp": 1699999999,
"sign": "MIICeQYJKoZIhvcNAQcCoIICajCCAnYCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEBBQUAMIGfMA0GCSqGSIb3DQEBBQUAA4GNADCBiQKBgQCq...(长字符串)"
}
步骤 2:定位签名函数
搜索 RSA、sign,找到核心函数(依赖 jsrsasign 库):
javascript
运行
// 私钥(前端硬编码,注意:实际场景中私钥可能不会直接暴露,此处为演示)
const PRIVATE_KEY = `-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMK...
-----END PRIVATE KEY-----`;
function generateRsaSign(params) {
// 1. 按参数名升序排序
const sortedKeys = Object.keys(params).sort();
// 2. 拼接参数(key=value 形式)
let signStr = '';
sortedKeys.forEach(key => {
signStr += `${key}=${params[key]}`;
});
// 3. RSA 签名(SHA256 哈希算法)
const rsa = new KJUR.crypto.Signature({ alg: 'SHA256withRSA' });
rsa.init(PRIVATE_KEY);
rsa.updateString(signStr);
// 4. 签名结果 Base64 编码
return rsa.signString();
}
步骤 3:Python 模拟实现
需安装依赖:pip install pycryptodome
python
运行
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
import base64
# 前端硬编码的私钥(需去除换行符和首尾标识)
PRIVATE_KEY_STR = """-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMK...
-----END PRIVATE KEY-----"""
def generate_rsa_sign(params):
# 1. 按参数名升序排序
sorted_keys = sorted(params.keys())
# 2. 拼接参数
sign_str = ''.join([f"{key}={params[key]}" for key in sorted_keys])
# 3. 加载私钥
private_key = RSA.import_key(PRIVATE_KEY_STR)
# 4. SHA256 哈希 + RSA 签名
hash_obj = SHA256.new(sign_str.encode('utf-8'))
signature = pkcs1_15.new(private_key).sign(hash_obj)
# 5. Base64 编码
return base64.b64encode(signature).decode('utf-8')
# 测试:生成 RSA 签名
params = {
'orderId': 'ORD12345678',
'amount': 99.9,
'timestamp': 1699999999
}
sign = generate_rsa_sign(params)
print(sign)
# 输出:与前端一致的长字符串签名
3. 常见变体与注意事项
- 签名算法:常见
SHA256withRSA、SHA1withRSA,需与前端一致; - 私钥格式:PEM 格式(含
BEGIN PRIVATE KEY标识),需去除换行符后导入; - 实际场景:前端通常不会直接暴露私钥,可能通过加密传输或后端签名后返回,需结合抓包分析密钥来源。
(四)SM2 签名:国密算法的安全防护
1. 算法特性
- 国密非对称加密算法(替代 RSA),密钥长度 256 位;
- 核心逻辑:参数拼接 → SM3 哈希 → SM2 私钥签名;
- 典型场景:国内政务系统、金融机构、国企接口(如社保查询、银行支付)。
2. 逆向步骤与案例
步骤 1:抓包分析请求
某政务接口,请求参数中 sign 为 SM2 签名:
plaintext
GET /api/social-security?userId=123456&date=2024-01-01&sign=MEUCIG...(国密签名串)
步骤 2:定位签名函数
搜索 SM2、sign,找到核心函数(依赖 sm-crypto 库):
javascript
运行
// 私钥(16 进制字符串)
const SM2_PRIVATE_KEY = '00B9AB0B828FF68872F21A837FC303668428DEA11DCD1B24429D0C99E24EED83D5';
function generateSm2Sign(params) {
// 1. 按参数名升序排序
const sortedKeys = Object.keys(params).sort();
// 2. 拼接参数
let signStr = '';
sortedKeys.forEach(key => {
signStr += `${key}=${params[key]}&`;
});
signStr = signStr.slice(0, -1); // 去除末尾 &
// 3. SM2 签名(SM3 哈希)
const sm2 = new SM2Utils();
const signature = sm2.sign(signStr, SM2_PRIVATE_KEY, {
hash: true, // 启用 SM3 哈希
der: true // 输出 DER 格式
});
return signature;
}
步骤 3:Python 模拟实现
需安装依赖:pip install gmssl
python
运行
from gmssl import sm2, func
import json
# 配置(与前端一致)
SM2_PRIVATE_KEY = '00B9AB0B828FF68872F21A837FC303668428DEA11DCD1B24429D0C99E24EED83D5'
SM2_PUBLIC_KEY = 'B9C9A6E04E9C91F7BA88042927374704B5EFE49391C02916169D9536967D6AFE' # 公钥(验证用)
def generate_sm2_sign(params):
# 1. 按参数名升序排序
sorted_keys = sorted(params.keys())
# 2. 拼接参数
sign_str = '&'.join([f"{key}={params[key]}" for key in sorted_keys])
# 3. 初始化 SM2 实例
sm2_crypto = sm2.CryptSM2(
public_key=SM2_PUBLIC_KEY,
private_key=SM2_PRIVATE_KEY
)
# 4. SM3 哈希 + SM2 签名(DER 格式)
sign_str_bytes = sign_str.encode('utf-8')
signature = sm2_crypto.sign(sign_str_bytes, func.random_hex(64)) # 随机数(64 位 16 进制)
return signature
# 测试:生成 SM2 签名
params = {
'userId': '123456',
'date': '2024-01-01'
}
sign = generate_sm2_sign(params)
print(sign)
# 输出:与前端一致的国密签名串
3. 常见变体与注意事项
- 签名格式:DER 格式(默认)或 RAW 格式,需与前端一致;
- 随机数:SM2 签名需随机数参与,前端可能使用固定随机数或动态生成,需逆向确认;
- 库依赖:前端常用
sm-crypto、gm-crypto,Python 对应gmssl,需确保算法参数一致。
三、签名算法逆向工具集
1. 抓包工具
- Chrome 开发者工具(Network 面板):快速抓包、筛选请求、查看参数;
- Fiddler/Charles:支持 HTTPS 解密、请求重放、参数修改;
- Mitmproxy:可编程抓包工具,适合批量处理和自动化测试。
2. 前端调试工具
- Chrome 开发者工具(Sources 面板):断点调试、变量监控、代码格式化;
- AST 反混淆工具(如
js-beautify、de4js):处理压缩、混淆的 JS 代码; - Hook 工具(如
Tampermonkey、Frida):拦截加密函数,获取输入输出参数。
3. Python 加密库
| 算法 | 依赖库 | 核心功能 |
|---|---|---|
| MD5 | 内置 hashlib |
哈希计算 |
| AES | pycryptodome |
对称加密 / 解密 |
| RSA | pycryptodome |
非对称签名 / 验证 |
| SM2 | gmssl |
国密签名 / 加密 |
四、避坑指南:常见问题与解决方案
1. 签名不一致?先查这 5 点
- 参数排序:是否与前端一致(ASCII 升序 / 降序);
- 字符串拼接:是否包含空值、是否有多余空格 / 符号;
- 编码格式:是否为 UTF-8,是否包含 BOM 头;
- 密钥 / IV:是否与前端完全一致(大小写、空格、编码);
- 算法细节:哈希算法(SHA256/SHA1)、填充方式(PKCS7/PKCS5)、签名格式(DER/RAW)。
2. 找不到签名函数?
- 关键词扩展:搜索
encrypt、decrypt、hash、sign、signature、token; - 抓包过滤:筛选包含签名参数的请求,查看其 Initiator(发起者)定位 JS 文件;
- 动态调试:在 Network 面板右键请求 → Copy → Copy as cURL,在终端执行,同时在 Sources 面板断点监控。
3. 密钥动态变化?
- 抓包分析:查看是否有前置接口返回密钥(如
/api/getKey); - 本地存储:检查 LocalStorage、SessionStorage、Cookie 中是否存储密钥;
- 动态生成:分析 JS 中密钥的生成逻辑(如基于设备信息、时间戳计算)。
4. 遇到反调试 / 反爬?
- 禁用断点调试:前端可能通过
debugger语句干扰调试,可在 Chrome 开发者工具中禁用; - 绕过证书验证:HTTPS 接口需配置 SSL 证书,避免因证书问题导致请求失败;
- 模拟真实环境:设置合理的 User-Agent、Referer、Cookie,避免被识别为爬虫。
五、总结
请求参数签名算法逆向的核心是 "还原前端逻辑",无论 MD5、AES、RSA 还是 SM2,都遵循 "定位 - 解析 - 实现 - 验证" 的流程。关键在于:
- 熟练使用抓包和调试工具,精准定位签名生成逻辑;
- 细致分析参数处理规则和算法细节,不遗漏任何一个小细节;
- 借助 Python 加密库快速实现,通过反复调试验证结果。
随着网站反爬技术的升级,签名算法可能会结合多种加密方式(如 AES+RSA 混合加密)或动态密钥,但核心思路不变。掌握本文的逆向方法和工具,即可应对绝大多数签名验证场景。实践是提升逆向能力的关键,建议多找真实接口练习,积累不同场景的处理经验。