前言
在接口逆向过程中,除了 AES 这种对称加密之外,RSA 也是非常常见的一类非对称加密方案。
很多站点会在前端使用 RSA 公钥对参数进行加密,再把密文提交给后端;服务端收到请求后,再使用私钥完成解密。
和 AES 不同,RSA 最大的特点在于:加密和解密使用的不是同一把钥匙。
也正因为这一点,RSA 很适合放在登录、密码传输、敏感参数保护这些场景里使用。
这篇文章主要整理三部分内容:
我对 RSA 这套逻辑的基本理解
JavaScript 中如何使用公钥进行加密
Python 中如何生成密钥对,以及如何完成加密和解密
代码本身依旧保留最直接的演示方式,不做额外封装,方便在逆向时逐步验证每个环节。
一、RSA 在逆向里重点看的是什么
在实际逆向里,RSA 的重点通常不是“手写算法实现”,而是先确认下面几个问题:
前端拿到的是不是 公钥
加密使用的是哪种填充方式
密文最终是以什么格式传输的
服务端是否使用对应私钥完成解密
如果前端代码里出现类似这种逻辑:
var enc = new JSEncrypt()
enc.setPublicKey("...")
var mi_text = enc.encrypt("...")
那基本就可以判断这是一个非常典型的 RSA 公钥加密 场景。通过 node-jsencrypt 创建对象,加载公钥,然后直接对明文字符串执行加密 。
从逆向角度看,这种场景的核心点有两个:
前端只需要公钥就可以完成加密
真正的解密动作通常发生在后端,因为私钥不会下发到前端
所以 RSA 在很多登录场景里经常会和 AES 配合出现:
前端先用 RSA 处理敏感字段,或者用 RSA 去加密 AES 密钥,再把 AES 用来处理大块业务数据。
二、我对 RSA 这套逻辑的理解
如果从实际使用过程来理解,RSA 可以拆成下面这套链路:
1. 先生成一对密钥
RSA 是非对称加密,核心在于它会生成一对密钥:
公钥
私钥
这两者是一一对应的,但用途不同。
2. 公钥用于加密
前端、客户端或者调用方拿到公钥之后,可以直接使用公钥对数据进行加密。
公钥本身可以公开。
3. 私钥用于解密
真正能把密文还原为明文的,是和这把公钥对应的私钥。
私钥一般只保存在服务端,不会暴露给前端。
4. 密文一般还会做 Base64 编码
因为 RSA 加密之后得到的是字节数据,很多场景里不会直接传原始字节,而是会进一步转成 Base64 字符串,方便传输。
这也是我在 Python 代码里的实际处理方式:先使用公钥加密,再把结果做 base64.b64encode(...);解密时则先 base64.b64decode(...),再交给私钥处理 。
三、JavaScript 中使用 RSA 进行加密
前端里比较常见的做法,是直接使用现成库进行 RSA 公钥加密。
我的代码使用的是 node-jsencrypt,它本质上是对 JSEncrypt 的封装,适合在 Node 环境中直接跑。我在代码里也专门提到了这一点:因为原生库里带有浏览器环境相关检测,所以在这里换成了更适合 Node 的版本 。
JS 示例源码
/**
* 安装依赖:
* npm install node-jsencrypt
*
* 这里使用 node-jsencrypt,而不是直接使用原始的 JSEncrypt。
* 原因是原始版本包含浏览器环境相关检测,在 Node 环境中使用时不够直接。
*/
var JSEncrypt = require("node-jsencrypt")
// 创建 RSA 加密对象
var rsaEncryptor = new JSEncrypt()
// 设置公钥
rsaEncryptor.setPublicKey(
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwK+rhBlEOYBuz3lM/jeU\n" +
"93diaPxjjmY2czoPgKul2er7K89Zwt2UACKh2jXoU4gfXxLodthsh1MlH4359pjE\n" +
"yN+CQJ1Mxd28u32jwLD1knOR9Z/nEbRzx49NL/dQ+M8gb2gc82eTdeQZFFCA4iLD\n" +
"qdJM2aREjMaLGSYTPtim1as1R5qDps7Hqwc70bRkbJCI/l79mooEatWQr7Yz/pBq\n" +
"VrADjF7F01MWagyhukhtfzuaMCBadvcv636fXJssJWJTWLXB1N9zloqzw9bGjYeA\n" +
"ias1eTq6ZySrxbgJkCNutk687mTr52w6+iSa8gvoma8x1mmBczhjgP+Fs6rHJWEA\n" +
"2QIDAQAB"
)
// 使用公钥对明文进行加密
var plainText = "我爱死你了!"
var cipherText = rsaEncryptor.encrypt(plainText)
// 输出加密结果
console.log(cipherText)
四、这段 JS 逻辑需要注意的点
1. 前端只负责公钥加密
从这段代码可以很清楚地看出来,前端只拿到了公钥,并没有私钥 。
这也是 RSA 在前端使用时最典型的形态:前端加密,后端解密。
2. setPublicKey() 是关键步骤
只要前端代码里定位到这一步,基本就能判断当前 RSA 的公钥来源。
很多站点里,这个公钥可能是直接写死在 JS 文件里,也可能是动态下发。
3. 加密后的结果通常已经是可传输字符串
JSEncrypt 这类库在很多场景里会直接返回适合传输的字符串结果,因此前端侧调用通常会比较直接。
五、Python 中使用 RSA 完成加密和解密
在Python 代码里,RSA 逻辑分成了两部分:
使用公钥加密
使用私钥解密
这里使用的是 Crypto.PublicKey.RSA 和 Crypto.Cipher.PKCS1_v1_5。
在代码中,我当前采用的是 PKCS1_v1_5 填充方式 。这也是很多实际站点里很常见的一种 RSA 使用方式。
Python 示例源码:RSA 加密与解密
# 导入 RSA 模块
from Crypto.PublicKey import RSA
# 导入 PKCS1_v1_5 加解密器
from Crypto.Cipher import PKCS1_v1_5
# 导入 Base64 模块
import base64
def rsa_encrypt():
# 1. 读取并导入公钥,后续使用公钥对数据进行加密
f_public = open("public_key.txt", mode="rb")
public_key_bytes = f_public.read()
f_public.close()
# 导入公钥
public_key = RSA.import_key(public_key_bytes)
# 2. 创建 RSA 加密器
rsa_public_cipher = PKCS1_v1_5.new(key=public_key)
# 3. 准备待加密明文
plain_text = "我爱你,就像老鼠爱大米!"
# 4. 执行 RSA 加密
encrypted_bytes = rsa_public_cipher.encrypt(plain_text.encode("utf-8"))
# 5. RSA 加密后的结果是字节数据,通常需要再转成 Base64 方便传输
cipher_text_base64 = base64.b64encode(encrypted_bytes).decode()
print("加密结果:", cipher_text_base64)
return cipher_text_base64
def rsa_decrypt(cipher_text_base64):
# 1. 读取并导入私钥
f_private = open("private_key.txt", mode="rb")
private_key_bytes = f_private.read()
f_private.close()
private_key = RSA.import_key(private_key_bytes)
# 2. 创建 RSA 解密器
rsa_private_cipher = PKCS1_v1_5.new(private_key)
# 3. 对 Base64 密文先进行解码,再使用私钥完成解密
decrypted_bytes = rsa_private_cipher.decrypt(base64.b64decode(cipher_text_base64), None)
# 4. 输出解密后的明文
print("明文:", decrypted_bytes.decode("utf-8"))
def main():
# cipher_text_base64 = rsa_encrypt()
cipher_text_base64 = "brgyuVFyPPo87JEJCwa06pj7HzRWtpNXdLwKd7uzR/L363FMkOGUv0DReK053vOMw0I4Q1aAKV7LlpOSog5RIuzIRfEGET3yZMQZoz1Ly45OgzNVRTvipPw0/rFTx0i9gUj7nZ/PuDcrWRCNL/QeNRrs9zgn1fCfNjSXboNru2cZ+gK2AxQb63t5B/g1wL0Kbc1stl6mbcQKOp+Iysif4C/I5JCjOjvez3ES807Jp1qeLjv8tpdfBWeae3eW2cjuEoRqnKG+zIQ3nIRKUHDoUM/1aZuSI7hoBIRy1HRxdZXbzraW7R1l06lZtrK8MGZ405CInmoiYs249IQvOAmTIg=="
rsa_decrypt(cipher_text_base64)
if __name__ == '__main__':
main()
这部分的逻辑:使用公钥文件加密、使用私钥文件解密、并通过 Base64 完成传输格式转换 。
六、Python 中如何生成 RSA 密钥对
在逆向验证时,如果只是想把 RSA 整套流程本地跑通,最方便的方式就是自己先生成一对密钥。
下面这份代码就是最直接的做法:调用 RSA.generate(2048) 生成密钥对,然后分别导出公钥和私钥,写入到本地文件中 。
Python 示例源码:生成 RSA 密钥对
from Crypto.PublicKey import RSA
import os
# RSA 是非对称加密,公钥和私钥并不相同。
# 实际使用时,公钥通常用于加密,私钥通常用于解密。
def generate_rsa_key_pair():
# 生成一对 2048 位的 RSA 密钥
rsa = RSA.generate(2048)
# 导出公钥
rsa_public_key = rsa.public_key().export_key()
f = open("public_key.txt", mode="wb")
f.write(rsa_public_key)
f.close()
# 导出私钥
rsa_private_key = rsa.export_key()
f = open("private_key.txt", mode="wb")
f.write(rsa_private_key)
f.close()
# def save_rsa_key():
# if not os.path.exists("public_key.txt" and "private_key.txt"):
# os.makedirs("public_key.txt" and "private_key.txt")
if __name__ == '__main__':
# save_rsa_key()
generate_rsa_key_pair()
这部分代码逻辑:先生成 RSA 对象,再导出公钥和私钥写入本地文件 。
七、这类 RSA 逻辑的标准处理流程
如果从流程上概括,这类 RSA 使用方式通常可以整理成下面几步。
密钥生成流程
生成 RSA 对象
-> 导出公钥
-> 导出私钥
-> 分别保存
加密流程
读取公钥
-> 导入公钥
-> 创建 RSA 公钥加密器
-> 对明文进行加密
-> Base64 编码
-> 得到最终密文
解密流程
读取私钥
-> 导入私钥
-> 创建 RSA 私钥解密器
-> Base64 解码
-> 使用私钥解密
-> 得到最终明文
八、RSA 在逆向里最容易出问题的几个地方
在实际调试过程中,RSA 复现最常见的问题通常集中在下面几个位置。
1. 公钥格式不对
有些站点给的是标准 PEM 格式公钥,有些给的是单纯的一段 Base64 公钥内容。
如果导入方式不对,通常会直接报错。
2. 填充方式不一致
我这份 Python 代码里使用的是 PKCS1_v1_5 。
如果目标站点使用的是其他填充方式,那么同样的公钥和明文,加密结果也不会一致。
3. RSA 明文长度有限制
这一点和 AES 不一样。
RSA 通常不适合直接加密很长的数据,更常见的是加密短文本、密码、token,或者用来加密 AES 密钥。
4. 密文传输格式判断错误
很多时候,RSA 加密结果并不是直接裸传,而是会进一步做 Base64 编码。
如果这一步没有对齐,解密时通常就会失败。
5. 前端通常只能做加密,不能做解密
因为前端一般只拿到公钥,不会拿到私钥。
所以如果在逆向时发现页面里只有公钥逻辑,而没有对应的解密逻辑,这是正常现象。
九、总结
在爬虫逆向里,RSA 的核心不在于手写算法,而在于把这套非对称加密链路看清楚:
公钥负责加密
私钥负责解密
加密结果通常还会再做 Base64 编码
前端通常只参与加密,后端负责解密
一旦把这些点确认下来,很多站点里的 RSA 逻辑其实都比较容易复现。
尤其是在登录、密码提交、密钥交换这类场景里,RSA 的使用频率非常高。对这类逻辑熟悉之后,处理接口逆向时会直接顺很多。