☰
Python 爬虫逆向实战 3:JS 加密接口 AES/RSA 参数破解与 sign 签名算法还原
2026/10/7 6:40:59 网站建设 项目流程

前言

主流电商、资讯接口通过前端 JS 对请求参数 AES 加密、RSA 密钥加密、MD5 + 随机盐生成 sign 签名,请求参数全密文传输,直接拼接明文参数请求返回验签失败。本章拆解前端 JS 逆向定位加密函数、抠取 JS 加密逻辑、Python 复刻加密算法,实现和前端一致的请求密文,适配 requests 直接调用加密接口,对接现有代理、UA、Cookie 整套爬虫工程。

本文所需依赖官方文档超链接:

  1. PyCryptodome 官方文档
  2. Requests 官方文档
  3. ExecJS 官方文档

一、接口签名加密分类与风控原理

1.1 三种主流加密方案

  1. AES 对称加密:固定密钥,前端后端共用同一密钥,参数整体 AES-CBC/ECB 加密;
  2. RSA 非对称加密:公钥前端加密参数,私钥后端解密,密钥拆分无法单靠密文反推原文;
  3. MD5-SIGN 签名:明文 + 固定盐值拼接后 MD5 哈希生成 sign,参数 + sign 一同提交,后端重复验签。

1.2 爬虫失效原因

直接构造明文参数提交,后端通过 sign / 密文校验判定非法请求,返回 403 / 数据为空。

二、环境依赖安装

bash

运行

pip install requests==2.31.0 pycryptodome==3.20.0 execjs==2.1.0

三、逆向排查步骤(浏览器抓包定位加密位置)

  1. F12 调试→Network 筛选 XHR/Fetch 接口,查看 Payload 密文参数;
  2. 右键调用堆栈 (Call stack) 定位加密 JS 函数;
  3. 断点调试,找到加密入口函数、密钥、偏移量、加盐字符串;
  4. 方案 1:抠 JS 代码用 execjs 直接调用;方案 2:Python 原生密码库复刻算法。

四、方案一:ExecJS 直接调用原生前端 JS(最简逆向)

4.1 提取前端加密 JS 代码示例(模拟前端 AES+sign)

javascript

运行

// encrypt.js const CryptoJS = require('crypto-js'); const KEY = "1234567812345678"; const SALT = "abc123xyz"; function aesEncrypt(data){ return CryptoJS.AES.encrypt(JSON.stringify(data), KEY, {mode:CryptoJS.mode.ECB,padding:CryptoJS.pad.Pkcs7}).toString() } function getSign(param){ let str = JSON.stringify(param)+SALT; return CryptoJS.MD5(str).toString() }

4.2 Python 调用 JS 实现参数加密

python

运行

import execjs import requests # 读取本地JS文件 with open("encrypt.js","r",encoding="utf-8") as f: js_code = f.read() ctx = execjs.compile(js_code) def get_encrypt_params(raw_dict): """明文字典传入JS,返回密文+sign""" encrypt_data = ctx.call("aesEncrypt",raw_dict) sign = ctx.call("getSign",raw_dict) return {"data":encrypt_data,"sign":sign} # 接口请求示例 HEADERS = {"User-Agent":"Mozilla/5.0 Chrome/124.0.0.0 Safari/537.36"} def api_crawl(url,plain_params): req_data = get_encrypt_params(plain_params) resp = requests.post(url,json=req_data,headers=HEADERS) return resp.json()

五、方案二:Python 原生 AES-ECB 算法复刻(脱离 JS 依赖)

python

运行

from Crypto.Cipher import AES from Crypto.Util.Padding import pad import json import hashlib AES_KEY = b"1234567812345678" SALT_STR = "abc123xyz" def aes_ecb_encrypt(raw_data:dict): """AES-ECB Pkcs7加密""" raw_str = json.dumps(raw_data,ensure_ascii=False).encode("utf-8") cipher = AES(AES_KEY,AES.MODE_ECB) pad_data = pad(raw_str,AES.block_size,style="pkcs7") encrypt_bytes = cipher.encrypt(pad_data) import base64 return base64.b64encode(encrypt_bytes).decode("utf-8") def md5_sign(raw_data:dict): """MD5加盐生成签名""" concat_str = json.dumps(raw_data,ensure_ascii=False)+SALT_STR md5_obj = hashlib.md5(concat_str.encode("utf-8")) return md5_obj.hexdigest() def build_req_body(plain): return {"data":aes_ecb_encrypt(plain),"sign":md5_sign(plain)}

六、拓展:RSA 前端公钥加密参数实现

python

运行

from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 import base64 # 前端扒取公钥字符串 RSA_PUB_KEY = """-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDxxx -----END PUBLIC KEY-----""" def rsa_encrypt(content:str): pub_key = RSA.import_key(RSA_PUB_KEY) cipher = PKCS1_v1_5.new(pub_key) encrypt_data = cipher.encrypt(content.encode("utf-8")) return base64.b64encode(encrypt_data).decode()

七、动态密钥场景:密钥从接口实时获取

部分站点密钥通过前置接口动态下发,每次请求密钥变更:

python

运行

def get_dynamic_key(): key_resp = requests.get("https://xxx.com/api/getkey",headers=HEADERS) return key_resp.json()["key"] def dynamic_aes_crawl(plain_dict): key = get_dynamic_key().encode("utf-8") cipher = AES(key,AES.MODE_ECB) # 后续加密逻辑复用上方AES代码

八、结合资源池完整爬虫示例(UA + 代理 + 加密接口三合一)

python

运行

import requests PROXY_API = "http://127.0.0.1:5010/get_proxy" def encrypt_full_crawl(target_api,plain_param): # 获取代理 proxy_info = requests.get(PROXY_API).json() proxies = {"http":f"http://{proxy_info['proxy']}","https":f"http://{proxy_info['proxy']}"} # 构造加密请求体 body = build_req_body(plain_param) headers = get_random_headers() res = requests.post(target_api,json=body,headers=headers,proxies=proxies,timeout=8) return res.json()

九、常见逆向踩坑与优化表

表格

问题解决方案
execjs 缺少 node 环境安装 Node.js,或改用 Python 原生密码库复刻
AES 加密结果和前端不一致核对模式 ECB/CBC、偏移 iv、填充格式 Pkcs7/Zero
sign 每次变化确认随机时间戳、nonce 字段参与签名拼接
RSA 超长参数加密前端分段 RSA 加密,Python 同步分段处理
密钥 JS 混淆无法提取AST 解混淆、在线 JS 格式化还原源码

十、Playwright 绕过加密懒人方案

不想逆向 JS 时,直接在浏览器环境调用页面原生加密函数:

python

运行

from playwright.sync_api import sync_playwright def playwright_get_encrypt_param(url,plain): with sync_playwright() as pw: browser = pw.chromium.launch(headless=True) page = browser.new_page() page.goto(url) # 调用页面全局加密函数 encrypt_res = page.evaluate("(arg)=>{return {data:aesEncrypt(arg),sign:getSign(arg)}}",plain) browser.close() return encrypt_res

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询