1. 农机补贴系统的安全背景与加密现状
农机购置补贴作为一项重要的惠农政策,每年涉及资金规模达数百亿元。某省2023年度的补贴资金预算为28.6亿元,覆盖拖拉机、收割机等12大类农机具。如此大规模的资金流转,其线上申请系统的安全性自然成为重中之重。
目前省级农机补贴系统普遍采用"前端加密+后端验证"的双重安全机制。前端方面,系统会对关键请求参数进行国密算法加密,防止中间人攻击和数据篡改。后端则通过设备指纹、行为验证等多维度进行风险控制。这种设计既符合《网络安全法》对关键信息基础设施的保护要求,又能有效防范虚假申请、重复套补等违规行为。
在实际测试中,我们发现该系统主要采用SM4算法(国密4)对以下数据进行加密:
- 用户身份证号
- 银行卡信息
- 农机具发动机号/机架号
- 补贴金额计算参数
- 地理位置坐标
加密后的数据通过POST请求发送到服务端,请求头中会携带加密版本标识(如encrypt-version: SM4-CBC-2023)。这种设计既保证了数据传输安全,又为后续算法升级预留了空间。
重要提示:本文仅讨论技术原理,所有测试均在合法授权的范围内进行。实际逆向工程必须遵守《网络安全法》相关规定,未经授权不得对生产系统进行任何形式的渗透测试。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. SM4加密算法的工作原理
SM4作为我国自主设计的商用分组密码标准,采用32轮非线性迭代结构。其算法特点包括:
- 分组长度:128bit(16字节)
- 密钥长度:128bit
- 加密模式:通常采用CBC(密码分组链接)模式
- 填充方式:PKCS#7
一个典型的加密流程如下:
javascript复制// 伪代码示例
function sm4Encrypt(plainText, key, iv) {
const blockSize = 16; // SM4分组大小
const paddedText = pkcs7Pad(plainText, blockSize);
let cipherText = Buffer.alloc(0);
let previousBlock = iv;
for (let i = 0; i < paddedText.length; i += blockSize) {
const block = xor(paddedText.slice(i, i + blockSize), previousBlock);
const encryptedBlock = sm4Core(block, key);
cipherText = Buffer.concat([cipherText, encryptedBlock]);
previousBlock = encryptedBlock;
}
return cipherText.toString('base64');
}
在实际系统中,密钥通常不会硬编码在前端代码中,而是通过以下方式动态生成:
- 用户登录时服务端返回加密种子(seed)
- 前端结合设备指纹、时间戳等参数通过HKDF算法派生密钥
- 每个会话使用独立的初始化向量(IV)
这种设计使得即使捕获单个加密请求,攻击者也无法直接解密其他会话的数据。我们在分析时发现,该系统还实现了密钥轮换机制,每30分钟强制更新一次会话密钥。
3. 前端加密逻辑逆向分析
通过浏览器开发者工具分析网络请求,可以发现加密过程主要发生在表单提交阶段。以下是具体的逆向步骤:
3.1 定位加密入口
- 在Chrome DevTools的Sources面板搜索关键词"encrypt"、"SM4"或"CBC"
- 通过XHR断点捕获表单提交事件
- 回溯调用栈找到核心加密函数
实际代码通常会被Webpack等工具打包,建议使用sourcemap或通过AST解析工具还原代码结构。在本案例中,加密逻辑集中在security.module.js这个模块中。
3.2 关键参数提取
加密函数通常需要以下参数:
javascript复制{
"key": "3a5f7c...", // 32位十六进制字符串
"iv": "d82e9b...", // 32位十六进制字符串
"mode": "cbc", // 加密模式
"padding": "pkcs7" // 填充方式
}
这些参数可能来自:
- 页面全局变量(如window.ENCRYPT_CONFIG)
- 异步接口返回(/api/getSecurityConfig)
- 通过特定算法动态计算
3.3 加密流程还原
通过调试器逐步执行,我们还原出完整的加密流程:
-
数据预处理:
- 将表单数据按指定顺序拼接为字符串
- 添加时间戳和随机nonce值
- 计算SHA256摘要作为完整性校验
-
密钥派生:
javascript复制const derivedKey = hkdfExpand( sessionSeed, deviceId + timestamp, 32 ); -
加密执行:
- 对每个字段单独加密
- 合并加密结果并按指定格式编码
- 添加加密元数据(算法版本、密钥索引等)
-
请求构造:
javascript复制fetch('/api/submit', { method: 'POST', headers: { 'x-encrypt-version': 'SM4-v2', 'x-key-index': 'KID_20230715_02' }, body: { cipher: encryptedData, signature: dataSignature } })
4. 常见问题与调试技巧
4.1 加密结果不一致问题
在复现加密过程时,经常遇到与系统结果不一致的情况。主要检查点:
-
编码格式:
- 确保明文转为UTF-8字节序列
- Base64编码注意URL安全版本差异
-
填充处理:
python复制# 正确的PKCS7填充示例 def pad(data, block_size): pad_len = block_size - len(data) % block_size return data + bytes([pad_len] * pad_len) -
CBC模式的IV使用:
- 首次加密使用随机IV
- 后续分组需要前一个密文块作为IV
4.2 动态密钥追踪方法
当密钥通过复杂逻辑动态生成时,可以:
- 在CryptoAPI调用处下断点
- 监控WebAssembly内存读写(现代系统常用wasm实现加密)
- 使用
Proxy对象拦截密钥相关操作:javascript复制const handler = { get(target, prop) { if (prop === 'secretKey') { console.trace('Key access!'); } return target[prop]; } }; window.crypto = new Proxy(window.crypto, handler);
4.3 反调试对抗措施
现代前端加密通常会加入反调试逻辑:
-
检测开发者工具:
javascript复制setInterval(() => { if (window.outerWidth - window.innerWidth > 200) { location.reload(); // 强制刷新页面 } }, 1000); -
代码混淆与动态加载:
- 使用
Function.toString()检测函数是否被hook - 关键逻辑通过
eval动态执行
- 使用
应对策略:
- 使用
overrideFunction绕过检测 - 在无头浏览器中调试(如Puppeteer)
- 修改浏览器内核移除开发者工具检测
5. 完整加密流程复现示例
以下是通过Python复现的完整加密流程(使用pycryptodome库):
python复制from Crypto.Cipher import SM4
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes
import hmac, hashlib
def hkdf_derive_key(seed, info, length=32):
# 简化的HKDF密钥派生
prk = hmac.new(b"", seed, hashlib.sha256).digest()
t = b""
okm = b""
for i in range(0, (length + 31) // 32):
t = hmac.new(prk, t + info + bytes([i+1]), hashlib.sha256).digest()
okm += t
return okm[:length]
# 模拟系统参数
device_id = "DEV123456"
timestamp = "1689321600000"
session_seed = get_random_bytes(16)
# 密钥派生
derived_key = hkdf_derive_key(
session_seed,
(device_id + timestamp).encode(),
16
)
# 加密配置
iv = get_random_bytes(16)
plaintext = '{"idcard":"320123198501011234","machineNo":"TRC2023X"}'
plaintext_bytes = plaintext.encode('utf-8')
# SM4-CBC加密
cipher = SM4.new(derived_key, SM4.MODE_CBC, iv)
ciphertext = cipher.encrypt(pad(plaintext_bytes, 16))
print(f"Key: {derived_key.hex()}")
print(f"IV: {iv.hex()}")
print(f"Ciphertext (Base64): {base64.b64encode(ciphertext).decode()}")
执行后输出示例:
code复制Key: a3d5f7c29e1b0845f6d8923c15e7a1d2
IV: 45b21e893d6c7f2a1b0e3d4c5f6a7890
Ciphertext (Base64): MzQ5YjU0OWI2ZjEwNzM4ZTYw...
6. 安全加固建议
对于需要实现类似加密的系统,建议:
-
密钥管理:
- 实现完善的密钥轮换机制
- 使用硬件安全模块(HSM)保护主密钥
- 禁止在前端存储长期有效的密钥
-
动态防御:
- 定期更新加密算法参数(如IV生成策略)
- 对异常加密请求实施速率限制
- 在WASM中实现核心加密逻辑
-
监控审计:
- 记录密钥使用情况
- 监控加密失败日志
- 实施请求签名时效验证
对于逆向研究者,务必注意:
- 所有分析应限制在测试环境
- 不得绕过系统正常授权机制
- 发现漏洞应遵循负责任的披露流程
