今天要拆解的是一个理财网站的数据请求加密机制,核心在于破解其采用的SHA256withRSA签名算法。这类金融类网站通常会对敏感接口进行严格加密,而本次遇到的正是典型的非对称加密应用场景。
通过初步抓包发现,该网站的查询接口(/queryMenu/disclosureSubject)在请求时携带了三个关键参数:
这种组合方式在金融行业非常普遍,主要目的是实现"防篡改+身份认证"双重保障。服务器持有公钥可验证签名真实性,而客户端用私钥签名确保请求来源可信。
使用Chrome开发者工具捕获到的请求示例如下:
http复制POST https://xinxipilu.chinawealth.com.cn/queryMenu/disclosureSubject HTTP/1.1
Content-Type: application/json
{
"i": "pageNum=1&pageSize=10",
"r": "aBcDeF...==",
"n": "MIIEvg..."
}
关键发现:
在JS逆向中,签名函数的定位往往是最关键的突破口。本次实践中尝试了三种搜索策略:
经验:加密参数名搜索建议优先尝试等号(=)作为后缀,比冒号(:)命中率更高
定位到的Me函数实现如下:
javascript复制function Me(e, t) {
const o = x.KEYUTIL.getKey(t)
, n = new x.KJUR.crypto.Signature({
alg: "SHA256withRSA"
});
return n.init(o),
n.updateString(e),
x.hextob64(n.sign())
}
代码解析:
KEYUTIL.getKey()加载PEM格式私钥Signature实例并指定算法为SHA256withRSA关键库依赖:
SHA256withRSA是Java风格的命名约定这种签名方案实际上是两种算法的组合:
具体流程:
code复制原始数据 → SHA256哈希 → RSA私钥加密 → Base64编码 → 最终签名
| 算法类型 | 哈希强度 | 密钥长度 | 典型应用场景 |
|---|---|---|---|
| MD5withRSA | 128位 | 2048位 | 历史遗留系统 |
| SHA1withRSA | 160位 | 2048位 | 传统数字证书 |
| SHA256withRSA | 256位 | 2048位 | 现代金融系统(本例) |
| SHA384withRSA | 384位 | 3072位 | 高安全需求场景 |
选择SHA256withRSA的三大优势:
推荐使用cryptography库(比PyCrypto更活跃维护),安装:
bash复制pip install cryptography
python复制import base64
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
def rsa_sign(data_str: str, private_key_pem: str) -> str:
"""
SHA256withRSA签名实现
:param data_str: 待签名字符串
:param private_key_pem: PEM格式私钥
:return: Base64编码的签名
"""
# 1. 加载私钥
private_key = serialization.load_pem_private_key(
private_key_pem.encode('utf-8'),
password=None,
)
# 2. 执行签名
signature = private_key.sign(
data_str.encode('utf-8'),
padding.PKCS1v15(),
hashes.SHA256()
)
# 3. Base64编码
return base64.b64encode(signature).decode('utf-8')
注意:如果私钥有密码保护,需要在load_pem_private_key中传入password参数
python复制import requests
def make_request(url: str, params: dict, private_key: str):
# 1. 构造查询字符串
query_str = '&'.join(f'{k}={v}' for k,v in params.items())
# 2. 生成签名
signature = rsa_sign(query_str, private_key)
# 3. 发送请求
payload = {
"i": query_str,
"r": signature,
"n": private_key # 实际业务中应预置密钥
}
return requests.post(url, json=payload).json()
当需要爬取多页数据时,需注意:
python复制for page in range(1, 6): # 爬取前5页
data = make_request(
"https://xinxipilu.chinawealth.com.cn/queryMenu/disclosureSubject",
{"pageNum": page, "pageSize": 20},
private_key
)
process_data(data)
time.sleep(random.uniform(1, 3))
| 现象 | 排查方向 | 解决方案 |
|---|---|---|
| 服务器返回签名无效 | 1. 公私钥不匹配 | 检查密钥对是否对应 |
| 2. 数据包含不可见字符 | 统一使用UTF-8编码 | |
| 报错"Invalid padding" | 填充方案不一致 | 确保两端都使用PKCS1v15 |
| 中文参数签名失败 | URL编码问题 | 对参数值单独编码后再拼接 |
密钥格式:
哈希计算:
Base64输出:
在实际爬虫开发中,建议采取以下安全措施:
密钥管理:
请求防护:
错误处理:
python复制try:
resp = make_request(...)
if resp.get('code') != 200:
raise Exception(f"API error: {resp.get('msg')}")
except requests.exceptions.SSLError:
# 处理证书错误
except ValueError:
# 处理密钥解析错误
通过这个案例可以清晰看到,现代Web应用的安全机制正在不断升级。作为爬虫开发者,我们需要深入理解这些加密原理,才能在合规的前提下实现数据采集需求。