一、 理论基石:理解12306的加密防御体系
1.1 为什么参数加密如此有效?
参数加密机制的核心在于:服务器通过验证请求参数的完整性和时效性来区分人类用户与机器程序。当您在网页上点击"查询"时,浏览器会执行复杂的JS代码,生成一个或多个经过加密的签名参数。这些参数往往具有:
● 时效性:与时间戳绑定,短时间内失效
● 唯一性:每次请求都会变化,防止重放攻击
● 关联性:与用户会话、查询条件等上下文关联
1.2 12306加密的典型特征
通过对12306网络请求的观察,我们可以发现以下典型特征:
● 动态令牌:如_json_att等字段,每次会话都会变化
● 签名参数:如查询接口中的leftTicketSecret等哈希值
● 加密密钥:某些接口参数使用非对称加密保护
二、 实战准备:搭建逆向分析环境
2.1 必备工具栈
● Chrome DevTools:核心分析工具(F12)
● Pretty-print:格式化压缩的JS代码({}按钮)
● Overrides:本地JS文件替换与调试
● Postman/Charles:API调试与抓包
● Python环境:执行JS代码(PyExecJS、Node.js)
2.2 关键分析步骤
逆向分析遵循"由外到内"的原则:
- 网络监控:识别关键API调用及加密参数
- 调用栈追踪:找到生成这些参数的JS函数
- 代码分析:理解加密逻辑与算法
- 代码移植:将JS逻辑转化为Python可执行代码
三、 深度实战:逆向12306查询接口加密参数
让我们以车票查询接口为例,进行完整的逆向分析。
3.1 识别加密参数
首先在浏览器中打开12306车票查询页面,开启Network监控,执行一次查询。观察请求的Query String Parameters,会发现类似以下的参数结构:
javascript// 原始请求参数 leftTicketDTO: { train_date: "2024-11-24", from_station: "BJP", to_station: "SHH", purpose_codes: "ADULT" } // 实际请求URL(示例) https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2024-11-24&leftTicketDTO.from_station=BJP&leftTicketDTO.to_station=SHH&purpose_codes=ADULT&_json_att=...
注意_json_att这个参数,它就是我们需要破解的加密参数之一。
3.2 定位加密函数
在Chrome DevTools中: - 搜索_json_att在所有JS文件中的出现位置
- 在初始化代码处设置断点
- 使用Call Stack追踪调用链
我们会发现关键代码通常隐藏在压缩的JS文件中。使用Pretty-print格式化后,可以找到类似这样的代码段:
javascript
```// 格式化后的关键JS代码片段
function generateJsonAtt() {
var e = Math.random().toString(36).substr(2);
var t = new Date().getTime();
var n = encryptMethod(e + "_" + t);
return window.btoa(n);
}
function encryptMethod(str) {
// 复杂的加密逻辑,可能涉及多个步骤
var key = CryptoJS.enc.Utf8.parse('1234567812345678');
var iv = CryptoJS.enc.Utf8.parse('1234567812345678');
var encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(str), key, {
keySize: 128 / 8,
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString();
}
3.3 算法分析与代码移植
分析上述代码,我们发现加密流程:
1. 生成随机字符串 + 时间戳
2. 使用AES-CBC模式加密
3. 进行Base64编码
现在我们需要在Python中重现这个逻辑:
python
```import execjs
import time
import random
import string
class Zhang12306JSEncrypt:
def __init__(self):
# 编译JS加密代码
with open('12306_encrypt.js', 'r', encoding='utf-8') as f:
js_code = f.read()
self.ctx = execjs.compile(js_code)
def generate_json_att(self):
"""生成_json_att参数"""
return self.ctx.call('generateJsonAtt')
# 对应的JS文件 (12306_encrypt.js)
"""
const CryptoJS = require('crypto-js');
function generateRandomString(length) {
return Math.random().toString(36).substr(2, length);
}
function generateJsonAtt() {
var random_str = generateRandomString(16);
var timestamp = new Date().getTime();
var raw_str = random_str + "_" + timestamp;
var encrypted = encryptMethod(raw_str);
return Buffer.from(encrypted.toString(), 'binary').toString('base64');
}
function encryptMethod(str) {
var key = CryptoJS.enc.Utf8.parse('1234567812345678');
var iv = CryptoJS.enc.Utf8.parse('1234567812345678');
var encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(str), key, {
keySize: 128 / 8,
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
}
module.exports = {
generateJsonAtt: generateJsonAtt
};
"""
3.4 完整请求示例
python
```import requests
from Zhang12306JSEncrypt import Zhang12306JSEncrypt
class Advanced12306Crawler:
def init(self):
self.session = requests.Session()
self.encryptor = Zhang12306JSEncrypt()
# 代理配置
self.proxyHost = "www.16yun.cn"
self.proxyPort = "5445"
self.proxyUser = "16QMSOML"
self.proxyPass = "280651"
# 设置请求头
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'https://kyfw.12306.cn/otn/leftTicket/init',
})
def get_proxies(self):
"""构造代理配置字典"""
proxy_url = f"http://{self.proxyUser}:{self.proxyPass}@{self.proxyHost}:{self.proxyPort}"
return {
'http': proxy_url,
'https': proxy_url
}
def query_tickets(self, train_date, from_station, to_station):
"""查询车票信息"""
# 生成加密参数
json_att = self.encryptor.generate_json_att()
# 构造请求参数
params = {
'leftTicketDTO.train_date': train_date,
'leftTicketDTO.from_station': from_station,
'leftTicketDTO.to_station': to_station,
'purpose_codes': 'ADULT',
'_json_att': json_att
}
url = 'https://kyfw.12306.cn/otn/leftTicket/query'
try:
# 使用代理发起请求
response = self.session.get(
url,
params=params,
proxies=self.get_proxies(),
timeout=10 # 添加超时设置
)
if response.status_code == 200:
data = response.json()
if data.get('status'):
return self.parse_ticket_data(data['data'])
else:
print("API返回状态错误:", data.get('messages', ['未知错误']))
else:
print(f"HTTP错误: {response.status_code}")
except requests.exceptions.ProxyError as e:
print(f"代理连接错误: {e}")
except requests.exceptions.ConnectTimeout as e:
print(f"连接超时: {e}")
except requests.exceptions.ReadTimeout as e:
print(f"读取超时: {e}")
except Exception as e:
print(f"请求异常: {e}")
return None
def parse_ticket_data(self, data):
"""解析车票数据"""
# 简化的解析逻辑
result = []
for item in data.get('result', []):
info = item.split('|')
if len(info) > 3:
result.append({
'车次': info[3],
'出发站': info[6],
'到达站': info[7],
'出发时间': info[8],
'到达时间': info[9],
'历时': info[10]
})
return result
使用示例
if name == "main":
crawler = Advanced12306Crawler()
tickets = crawler.query_tickets('2024-11-24', 'BJP', 'SHH')
if tickets:
for ticket in tickets[:3]:
print(ticket)
四、 高级技巧:应对动态变化的加密逻辑
4.1 处理代码混淆与压缩
12306的JS代码经常更新且高度混淆。应对策略:
● 使用AST(抽象语法树)工具进行反混淆
● 建立代码特征库,快速定位关键函数
● 监控JS文件变化,自动触发重新分析
4.2 自动化更新机制
python
```import hashlib
import os
class AutoUpdateEncryptor:
def __init__(self, js_url):
self.js_url = js_url
self.local_file = '12306_encrypt.js'
self.ctx = None
self.load_js()
def get_js_md5(self):
"""计算JS文件MD5以检测变化"""
with open(self.local_file, 'rb') as f:
return hashlib.md5(f.read()).hexdigest()
def download_js(self):
"""下载最新JS文件"""
# 实现下载逻辑
pass
def load_js(self):
"""加载JS执行环境"""
if not os.path.exists(self.local_file):
self.download_js()
with open(self.local_file, 'r', encoding='utf-8') as f:
js_code = f.read()
self.ctx = execjs.compile(js_code)
def check_and_update(self):
"""检查并更新JS文件"""
# 定期检查JS文件是否更新
pass
五、 伦理边界与最佳实践
5.1 合法合规使用
严格遵守robots.txt协议
控制请求频率,避免对服务器造成压力
仅用于技术学习与研究目的
5.2 技术防护措施
实现请求失败的重试机制
使用IP代理池分散请求
建立完整的日志监控系统
结论:逆向工程的艺术与科学
破解12306的JS加密参数,是一场在技术边界上的精确舞蹈。它既需要扎实的JavaScript语言基础,又需要对加密算法的深刻理解,更需要耐心细致的调试分析能力。
通过本文的深度剖析,我们不仅掌握了一套具体的技术方案,更重要的是建立了一种逆向思维的方**法论。在面对任何复杂的反爬虫机制时,我们都能够:
冷静分析:从网络请求入手,识别关键参数
精准定位:利用开发者工具,追踪调用链路
深度还原:分析加密逻辑,重现算法流程
工程实现:构建稳定可靠的爬虫系统