1. 项目概述:当爬虫遇上AES加密
最近在分析一个数据源时,又碰到了老朋友——AES加密。请求参数或者返回的数据,不再是明晃晃的JSON字符串,而是一串看着像乱码的密文。对于刚接触JS逆向的朋友来说,这堵“加密墙”往往就是第一个拦路虎。今天,我就结合一个实战案例,把AES加密在JavaScript中的实现原理、逆向分析方法以及如何在Python中复现解密过程,掰开揉碎了讲清楚。无论你是想学习JS逆向的爬虫工程师,还是对前端加密实现好奇的开发者,这篇文章都能给你一套清晰的“解题思路”和“操作手册”。我们不止要会用工具,更要明白背后的门道,这样下次遇到变种的加密方式,你也能自己找到突破口。
2. AES加密核心原理与模式解析
要逆向AES,首先得知道它正着是怎么工作的。AES(Advanced Encryption Standard)是一种对称加密算法,意思是加密和解密用的是同一把钥匙,我们称之为密钥。它的核心思想是把数据分成一个个固定大小的“块”,然后通过多轮的“混淆”和“扩散”操作,让原始数据和密钥充分混合,最终输出密文。
2.1 关键参数:密钥、初始向量与填充
AES加密不是简单地输入明文和密钥就能出密文,它依赖于几个关键参数,理解这些是逆向的基础:
密钥(Key) :这是加密解密的根本。AES标准支持三种长度的密钥:128位(16字节)、192位(24字节)和256位(32字节)。密钥越长,安全性越高,但计算也稍慢。在Web前端,为了兼顾安全与性能,128位和256位比较常见。
初始向量(IV, Initialization Vector) :这不是所有模式都需要的,但在最常见的CBC(Cipher Block Chaining)模式下,IV至关重要。它是一个随机生成的、长度与加密块大小(AES固定为128位,即16字节)相同的字节序列。IV的作用是确保即使相同的明文用相同的密钥加密,每次产生的密文也不同,防止攻击者通过对比密文模式来推测信息。 在逆向时,如果发现每次请求的密文都不同,但用同一个密钥又能解密,那几乎可以肯定使用了CBC模式且有随机IV。
填充(Padding) :AES是块加密,一次处理一个128位的块。如果明文长度不是16字节的整数倍怎么办?这就需要填充。常见的填充方式有PKCS#7(也叫PKCS#5)。它的规则很简单:缺几个字节,就用几来填充。例如,一个15字节的数据,需要补1个字节,填充值就是 0x01 ;如果是14字节,就补两个 0x02 。解密后,需要正确移除这些填充字节才能得到原始数据。
注意 :在JavaScript的CryptoJS库或Node.js的crypto模块中,默认的填充方式往往是PKCS#7。但在一些“魔改”的加密实现里,开发者可能会自定义填充规则,这是逆向中的一个常见坑点。
2.2 工作模式:CBC与ECB
AES有不同的工作模式,决定了块与块之间如何关联。逆向时必须确定模式。
- ECB(Electronic Codebook) :最简单的模式,每个数据块独立加密。相同的明文块会产生相同的密文块。这会导致模式泄露,安全性很差,在Web中已很少用于敏感数据,但逆向时如果发现没有IV参数,可能就是它。
- CBC(Cipher Block Chaining) :目前最常用的模式。它引入了IV,并且每个明文块在加密前,会先与前一个密文块进行异或操作(第一个块与IV异或)。这种链式结构让密文块之间产生了依赖,安全性大大增强。 绝大多数网站的前端AES加密都采用CBC模式。
在逆向分析时,我们首要目标就是找到这三个要素: 密钥(Key)、初始向量(IV)和工作模式(通常是CBC) 。它们可能硬编码在JavaScript文件里,也可能通过某个接口动态获取,或者由其他参数计算得出。
3. JavaScript中的AES加密实现与逆向切入点
前端实现AES加密,最常用的库是CryptoJS。它是一个功能强大的加密算法库,提供了简洁的API。理解它的常见用法,就等于拿到了逆向的“地图”。
3.1 CryptoJS的典型用法
一个标准的CryptoJS AES-CBC加密代码示例如下:
// 引入CryptoJS(在Web中通常通过CDN)
// <script src=\”https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js\”></script>
// 假设这是要加密的数据
var plainText = \'{\”page\”:1,\”size\”:20}\’;
// 定义密钥和IV(这里以字符串形式,实际会转换为WordArray)
var key = CryptoJS.enc.Utf8.parse(\’1234567890123456\’); // 16字节密钥
var iv = CryptoJS.enc.Utf8.parse(\’abcdefghijklmnop\’); // 16字节IV
// 执行AES-CBC加密
var encrypted = CryptoJS.AES.encrypt(plainText, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7 // 默认就是Pkcs7,常省略
});
// 将加密结果转换为Base64字符串(常见传输格式)
var ciphertextBase64 = encrypted.toString();
console.log(ciphertextBase64); // 输出类似:\’U2FsdGVkX1+…\’
逆向时的关键观察点 :
