抓包全是密文?AES+RSA混合加密接口逆向全流程实战
做工业数据采集的朋友,大概率都碰过这种硬骨头:接口地址一清二楚,请求响应也能正常抓到,可参数和返回体全是一长串无意义的字符串,肉眼根本读不出结构。普通的接口采集方案直接失效,想拿到结构化数据,就得先过加密这道关。
在所有传输层防护方案里,AES+RSA混合加密是普及率最高的一种,保守估计占了加密接口的90%以上。看起来复杂,其实套路非常固定,只要摸清楚了标准流程,绝大多数加密接口都能按图索骥破解掉。
今天这篇文章,我把混合加密的原理、逆向全流程、参数对齐技巧、常见踩坑点一次性讲透,看完你就能独立搞定绝大多数AES/RSA加密接口。
一、前期准备:先搞懂混合加密的底层逻辑
1.1 为什么一定是AES+RSA混合?
单独用AES(对称加密),加解密速度快,但密钥需要和数据一起传输,很容易被截获;单独用RSA(非对称加密),安全性高但加密速度极慢,稍长一点的业务数据就扛不住性能开销。
混合加密就是取两者的长处,是工业界通用的标准方案:
- 用RSA传输AES的随机密钥(密钥很短,非对称加密完全扛得住)
- 用AES加密实际业务数据(速度快,适合大批量业务数据传输)
站点不会凭空发明加密算法,绝大多数都是基于这套标准逻辑做的封装,这也是逆向有章可循的根本原因。
1.2 混合加密标准交互流程
简单来说就是:非对称加密管密钥,对称加密管数据。一次请求生成一次随机密钥,既保证了传输安全,又不损失性能。
1.3 快速识别:一眼判断是不是混合加密
不用翻代码,抓包看字段就能猜个八九不离十:
- 请求体里通常有两个核心字段:一个短的固定长度字段(加密后的AES密钥),一个长的可变长度字段(加密后的业务数据)
- 字段名常见为:encryptKey、secretKey、ciphertext、encryptData、data等
- 密文格式多为Base64编码,偶尔是Hex字符串
- 2048位RSA加密后的密钥,Base64编码后约344字符;1024位约172字符,抓包就能大致判断密钥长度
符合这几个特征,90%概率就是标准的AES+RSA混合加密。
1.4 工具准备
- 浏览器开发者工具(F12):核心调试工具,用来断点定位加密逻辑
- Python 3.x:最终的代码复现环境
- pycryptodome库:Python侧的加解密实现,安装命令
pip install pycryptodome - 可选:Node.js环境,用来运行扣取的JS代码做对比验证
二、分步实操:从抓包到Python复现完整流程
2.1 第一步:抓包定位,锁定加密字段
先不要急着翻代码,第一步先把输入输出搞清楚,后面调试才有参照。
- 找一个有明确业务含义的请求,比如列表页、详情页接口,确保能稳定复现
- 对比请求参数和页面展示内容,确认哪些字段是加密的,明文大致是什么结构
- 记录加密字段的名称、编码格式(Base64/Hex)、大致长度
- 同样确认响应体的加密字段位置和格式
这一步的核心是明确目标:我们要复现的是哪个函数的输入输出,避免后面调试的时候漫无目的。
2.2 第二步:断点调试,定位加密入口
这是逆向最关键的一步,找到JS里执行加密的具体位置。三种常用方法,按效率从高到低排序:
1. 关键词搜索法(优先用)
直接在Sources面板全局搜索关键词:encrypt、AES、RSA、CryptoJS、cipher、encryptKey,大概率能直接定位到加密函数。绝大多数站点的加密代码不会过度混淆函数名,搜关键词是最快的方式。
2. XHR断点回溯法
如果关键词搜不到,给目标接口打XHR断点,刷新页面触发请求后,查看调用栈从发起请求的位置往前回溯,找参数组装和处理的逻辑,加密函数一定在请求发出之前的调用链上。
3. Hook拦截法
针对JSON.stringify、btoa(Base64编码)这类通用方法打Hook,在加密前的明文处断下来,顺着调用栈往上找,就能找到调用加密函数的位置。
定位到加密函数后,先打个断点,触发一次请求,看清楚入参和出参:入参是明文业务数据,出参是加密后的密文和密钥。
2.3 第三步:提取算法参数,抠出核心逻辑
找到加密函数后,不要急着全量扣代码,先确认几个核心参数——这是后续复现的关键,80%的复现失败都是参数没对齐。
AES部分必须确认的参数:
- 算法模式:最常见的是CBC,其次是ECB,GCM模式也逐渐增多
- 填充方式:绝大多数是Pkcs7,对应Python里的PKCS7填充
- IV偏移量:CBC/GCM模式必须有,ECB模式没有,长度固定16字节
- 密钥长度:128位(16字节)最常见,256位(32字节)次之
RSA部分必须确认的参数:
- 填充方式:PKCS1_v1_5最普遍,少数站点用OAEP填充
- 公钥内容:一般是固定的PEM格式字符串,或者硬编码的模数+指数
- 密钥长度:1024位和2048位为主
把这些参数和公钥提取出来,再确认密文的输出编码格式,基本就完成了80%的工作。
2.4 第四步:Python侧复现加解密
拿到所有参数后,就可以用Python的Crypto库实现加解密了。下面给出标准实现,覆盖绝大多数场景。
AES加解密核心实现:
fromCrypto.CipherimportAESfromCrypto.Util.Paddingimportpad,unpadimportbase64classAesUtil:def__init__(self,key,iv=None,mode=AES.MODE_CBC):self.key=key.encode('utf-8')self.mode=mode self.iv=iv.encode('utf-8')ifivelseNonedefencrypt(self,plaintext):cipher=AES.new(self.key,self.mode,self.iv)padded_data=pad(plaintext.encode('utf-8'),AES.block_size)returnbase64.b64encode(cipher.encrypt(padded_data)).decode('utf-8')defdecrypt(self,ciphertext):cipher=AES.new(self.key,self.mode,self.iv)cipher_bytes=base64.b64decode(ciphertext)plain_bytes=unpad(cipher.decrypt(cipher_bytes),AES.block_size)returnplain_bytes.decode('utf-8')RSA公钥加密核心实现:
fromCrypto.CipherimportPKCS1_v1_5fromCrypto.PublicKeyimportRSAimportbase64classRsaUtil:def__init__(self,public_key_str):# 自动补全PEM格式公钥头尾ifnotpublic_key_str.startswith('-----BEGIN'):public_key_str=('-----BEGIN PUBLIC KEY-----\n'+public_key_str+'\n-----END PUBLIC KEY-----')self.public_key=RSA.import_key(public_key_str)defencrypt(self,plaintext):cipher=PKCS1_v1_5.new(self.public_key)cipher_bytes=cipher.encrypt(plaintext.encode('utf-8'))returnbase64.b64encode(cipher_bytes).decode('utf-8')混合加密调用逻辑:
importsecrets# 1. 生成16位随机AES密钥aes_key=secrets.token_hex(8)aes_iv='1234567890123456'# 实际值以站点逻辑为准# 2. RSA加密AES密钥rsa_util=RsaUtil(public_key_str)encrypt_key=rsa_util.encrypt(aes_key)# 3. AES加密业务数据aes_util=AesUtil(aes_key,aes_iv)encrypt_data=aes_util.encrypt('{"page":1,"pageSize":10}')# 4. 组装最终请求参数payload={'encryptKey':encrypt_key,'encryptData':encrypt_data}响应解密逻辑更简单:用同一个AES密钥,直接解密返回的加密数据字段即可。
三、问题排查:90%的复现失败都栽在这几个坑
很多人逆向的时候,算法名对上了,密钥也拿到了,可就是加密结果和站点不一样,或者解密出来全是乱码。绝大多数问题都出在细节参数上,挨个排查就行。
坑1:填充方式不匹配
这是最高频的错误。AES的填充有Pkcs7、Pkcs5、ZeroPadding等,JS端CryptoJS默认是Pkcs7,Python里要显式指定,很多人用了默认无填充,结果密文永远对不上。
RSA同理,PKCS1_v1_5和OAEP是完全不同的填充方式,搞混了直接解密失败。
坑2:IV偏移量遗漏或错误
AES的CBC模式必须有IV,长度固定16字节。很多站点的IV是硬编码在JS里的,或者和密钥一起生成,逆向的时候很容易漏掉。
注意ECB模式不需要IV,传了反而会出错。模式和IV一定要严格对应。
坑3:编码格式混乱
密文输出到底是Base64还是Hex字符串,必须和站点完全一致。JS里CryptoJS默认toString()输出Hex,很多站点会额外转成Base64传输。
另外明文的字符集也要注意,绝大多数是UTF-8,少数老站点可能用GBK,解密乱码的时候可以优先排查。
坑4:RSA公钥格式不对
很多站点的公钥是去掉头尾的纯Base64字符串,导入的时候必须补全PEM格式的头尾标记,否则RSA库会直接导入失败。
还有部分站点用模数(n)和指数(e)的形式存储公钥,需要自己组装成公钥对象,不能直接当PEM格式导入。
坑5:字节处理差异
JS里的字符串是UTF-16编码,而Python操作的是字节串。尤其是处理中文的时候,如果编码转换没做好,很容易出现明文一致但密文不同的情况。
最稳妥的验证方法:两边都用同样的纯英文测试明文,先对齐纯英文的结果,再处理中文编码问题。
坑6:密钥长度错误
AES密钥必须是16/24/32字节,对应128/192/256位,少一个字节都会直接报错。RSA公钥的长度要和密文长度对应,拿不准的时候可以通过抓包的密文长度反推。
四、进阶拓展:遇到混淆代码怎么办?
上面讲的是明文JS的情况,实际项目里很多站点的JS代码是混淆过的,变量名全是无意义字符,加密函数藏得很深。分享三个实用的应对方案,按成本从低到高排序。
1. 直接调用JS法
不用费劲扣代码,用PyExecJS库直接在Python里运行JS代码,把明文传进去直接拿密文。优点是简单快捷,不用关心内部逻辑;缺点是性能一般,适合小规模采集场景。
2. 补环境运行法
把混淆后的JS文件整体拿下来,在Node.js里补全浏览器环境(window、document、navigator等对象),直接调用加密函数。适合混淆严重但加密入口明确的场景,是平衡效率和成本的最优解。
3. AST还原法
用Babel等工具对混淆JS做AST解析,还原变量名、拆分控制流、去除死代码,把混淆代码还原成可读形式。适合高强度混淆、需要深度分析的场景,门槛相对高一些。
对绝大多数采集场景来说,前两种方案就足够用了。能用工程方法解决的问题,就不要死啃代码逆向。
五、写在最后
加密接口逆向看起来门槛很高,其实核心难点从来不是算法本身——AES和RSA都是公开的标准算法,Python有成熟的库可以直接调用。真正考验人的,是定位加密入口、对齐细节参数的过程。
总结下来就是三步走:先抓包确认加密方案,再断点调试提取核心参数,最后用Python复现加解密逻辑。绝大多数混合加密接口,都逃不开这个固定套路。
最后还是要提醒一句:接口逆向技术请仅用于合法合规的技术研究与公开数据采集,遵守目标站点的服务条款与robots协议,不要用于非法用途。技术是工具,底线不能丢。
