爬虫逆向中常见的 RSA 加密逻辑分析与复现

fengxiaoyao
17
2026-02-13

前言

在接口逆向过程中,除了 AES 这种对称加密之外,RSA 也是非常常见的一类非对称加密方案。
很多站点会在前端使用 RSA 公钥对参数进行加密,再把密文提交给后端;服务端收到请求后,再使用私钥完成解密。

和 AES 不同,RSA 最大的特点在于:加密和解密使用的不是同一把钥匙
也正因为这一点,RSA 很适合放在登录、密码传输、敏感参数保护这些场景里使用。

这篇文章主要整理三部分内容:

  1. 我对 RSA 这套逻辑的基本理解

  2. JavaScript 中如何使用公钥进行加密

  3. Python 中如何生成密钥对,以及如何完成加密和解密

代码本身依旧保留最直接的演示方式,不做额外封装,方便在逆向时逐步验证每个环节。


一、RSA 在逆向里重点看的是什么

在实际逆向里,RSA 的重点通常不是“手写算法实现”,而是先确认下面几个问题:

  1. 前端拿到的是不是 公钥

  2. 加密使用的是哪种填充方式

  3. 密文最终是以什么格式传输的

  4. 服务端是否使用对应私钥完成解密

如果前端代码里出现类似这种逻辑:

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 逻辑分成了两部分:

  1. 使用公钥加密

  2. 使用私钥解密

这里使用的是 Crypto.PublicKey.RSACrypto.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 的使用频率非常高。对这类逻辑熟悉之后,处理接口逆向时会直接顺很多。