一、对称加密的困境与 RSA 起源

在RSA诞生之前,对称加密是数据加密的主流方案:

  • 核心逻辑:加密与解密使用同一把密钥,例如DES、AES算法。

  • ✅优势:运算速度快,适合对大量数据进行加密。

  • ❌致命缺陷密钥分发难题

通信双方必须提前安全交换密钥,一旦密钥在传输过程中被窃取,所有加密通信内容将彻底暴露。

比如:你想给朋友发送加密消息,必须先把密钥传递给他,但传输密钥的过程本身就存在被窃听的风险。

为了解决这一难题,非对称加密技术应运而生。1977年,Rivest、Shamir和Adleman三位学者提出了RSA算法,它通过一对密钥(公钥+私钥)的设计,彻底解决了对称加密的密钥分发问题。

二、RSA 核心概念

1. 密钥对

RSA使用一对相互关联的密钥:

  • 公钥(Public Key):可以公开给任何人,用于加密数据或验证数字签名。

  • 私钥(Private Key):必须由持有者严格保密,用于解密数据或生成数字签名。

2. 加密与解密逻辑

  • 加密场景:任何人都可以使用你的公钥加密信息,但只有持有私钥的你才能解密。

  • 签名场景:你可以用私钥对信息摘要进行签名,其他人用公钥验证签名,确保数据未被篡改且来源可靠。

一句话总结:公钥负责加密/验签,私钥负责解密/签名,两者无法互相推导,这是RSA安全性的基础。

三、RSA 数学原理

RSA的安全性建立在大数质因数分解的数学难题上:已知两个大质数pq,计算乘积n = p*q很容易,但已知n反推出pq在计算上几乎不可行。

1. 密钥生成步骤

  • 选择质数:随机选取两个大质数pq(实际应用中通常为数百位的大数)。

  • 计算模数nn = p × qn是公钥的组成部分。

  • 计算欧拉函数φ(n)φ(n) = (p-1) × (q-1),只有知道pq的人才能计算此值。

  • 选择公钥指数e:选取一个与φ(n)互质的整数e(常用值为65537,因其为质数且计算高效)。

  • 计算私钥指数d:求解满足(e × d) mod φ(n) = 1的整数dd是私钥的核心。

最终得到:

  • 公钥:(n, e)

  • 私钥:(n, d)

2. 加密与解密公式

  • 加密:将明文M转换为数字,计算密文 C = M^e mod n

  • 解密:将密文C还原为明文 M = C^d mod n

3. 极简示例

假设:

  • p=3q=11n=33

  • φ(n)=(3-1)×(11-1)=20

  • e=3(与20互质),d=7(满足3×7 mod 20 = 1

加密明文M=5

  • 加密:C = 5³ mod 33 = 125 mod 33 = 26

  • 解密:M = 26⁷ mod 33 = 5

通过这个过程,原始信息得以安全加密并传输,只有持有私钥的一方能够解密。

四、RSA 的局限性与实际应用

1. 三大局限性

  1. 私钥保密性:私钥一旦泄露,所有加密数据将被破解,必须严格保管。

  2. 数据长度限制:RSA能加密的明文长度远小于密钥长度,无法直接加密大文件。

  3. 计算成本高:大数幂运算耗时远高于对称加密,不适合高频大数据传输场景。

2. 实际应用场景

RSA的核心应用场景是在HTTPS协议中。当我们访问一个HTTPS网站时,浏览器与服务器之间会进行一次安全的握手过程:

  • 服务器发送其公钥给浏览器。

  • 浏览器生成一个随机的对称加密密钥,并使用服务器的公钥加密后发送回去。

  • 服务器使用自己的私钥解密,得到对称密钥。

之后,双方使用对称加密算法(如AES)进行快速的加密和解密操作,而RSA的作用仅限于安全地交换密钥。

五、在 JavaScript 中实现 RSA 加密

1. 安装依赖

在使用 RSA 加密之前,首先需要安装 Node.js 的 node-rsa 模块。打开终端,运行以下命令:

npm install node-rsa --save

2. 引入模块

安装完成后,在你的 JavaScript 文件中引入node-rsa模块

const NodeRSA = require('node-rsa');

3.生成公钥和私钥

使用 NodeRSA 来生成一对密钥,RSA 的安全性和密钥长度相关(密钥长度越长,安全性越高,但是加密和解密速度越慢),常见的密钥长度有 1024 位、2048 位等(生产环境至少用2048位)。为了演示,使用 512 位密钥:

const rsa_encrypt = new NodeRSA({ b: 512 });

4.导出公钥和私钥

生成密钥后,可以通过 exportKey() 方法导出公钥和私钥

let publicKey = rsa_encrypt.exportKey('public');    // PEM格式公钥
let privateKey = rsa_encrypt.exportKey('private');    // PEM格式私钥

PEM格式(Privacy Enhanced Mail)是一种文本格式的编码规范,最初为安全电子邮件设计,现在广泛用于存储和传输加密密钥、证书、签名等安全相关数据

5.使用公钥进行加密

使用 encrypt() 方法可以使用公钥加密数据。以下是加密字符串 "你好" 的示例:

const NodeRSA = require('node-rsa');

let rsa_encrypt = new NodeRSA({b:512});
let publicKey = rsa_encrypt.exportKey('public');
let privateKey = rsa_encrypt.exportKey('private');

let text = '你好';

let encryptedData = rsa_encrypt.encrypt(text);

console.log(encryptedData);


#输出:<Buffer 34 e0 dc b8 ac 33 49 2c 4d 1c c4 db 54 0a ee 3d 0c 5f 61 4f f2 cd f5 f7 9a 62 d2 2f 9d 92 fb 7a 5c 4d c5 07 f2 a5 82 6d bd 92 a0 ed e4 bb 99 24 06 16 ... 14 more bytes>

encrypt()默认输出的是一个Buffer对象,相当于是二进制数据的容器。

将打印结果转换成字符串有两种方法:

  • encrypt()方法中直接指定编码方式,比如最常见的编码就是 base64

    let encryptedData = rsa_encrypt.encrypt(text, 'base64');
  • 使用 Node.js 自带的 Buffer.toString() 方法手动转换。

    let encryptedData = rsa_encrypt.encrypt(text);
    const encryptedBase64 = encryptedData.toString('base64');

完整的.js代码如下

const NodeRSA = require('node-rsa');

let rsa_encrypt = new NodeRSA({b:512});
let publicKey = rsa_encrypt.exportKey('public');
let privateKey = rsa_encrypt.exportKey('private');

let text = '你好';

let encryptedData = rsa_encrypt.encrypt(text, 'base64');
//let encryptedData = rsa_encrypt.encrypt(text);
//const encryptedBase64 = encryptedData.toString('base64');

console.log(encryptedData);

6.解密加密数据

要解密数据,可以使用 decrypt() 方法。解密时需要指定编码方式为 'utf8' 来正确显示文本:

const NodeRSA = require('node-rsa');

let rsa_encrypt = new NodeRSA({b:512});
let publicKey = rsa_encrypt.exportKey('public');
let privateKey = rsa_encrypt.exportKey('private');

let text = '你好'
let encryptedData = rsa_encrypt.encrypt(text, 'base64');

let decryptesData = rsa_encrypt.decrypt(encryptedData, 'utf8');
console.log(decryptesData);

RSA 加密的特性

RSA 的加密结果每次都不同,即使加密的明文相同。这是因为 RSA 使用了填充机制(如 PKCS1 或 OAEP 填充),随机数据被加入到加密的明文中,保证每次加密的结果不同,增加安全性。

六、在 Python 中实现 RSA 加密

1. 安装依赖

在 Python 中,我们使用 rsa 库进行 RSA 加密。首先在终端中装该库:

pip install rsa

2.导入库

安装完成后,使用以下代码导入 rsa 模块:import rsa

3.生成公钥和私钥

使用 rsa.newkeys() 方法生成 RSA 密钥对:

import rsa

key = rsa.newkeys(512)
public_key = key[0]
private_key = key[1]

向rsa.newkeys() 方法传入密钥长度,会随机生成指定长度的公钥和密钥,并以元组的形式返回。

2. 基础加密/解密示例

import rsa
import base64  # 导入Base64编码库,用于将二进制密文转成可打印的字符串

# 生成512位RSA密钥对(公钥用于加密,私钥用于解密)
public_key, private_key = rsa.newkeys(512)

# 加密流程
text = "你好,RSA!"  # 待加密的原始字符串(中文需指定编码)
encrypted = rsa.encrypt(text.encode('utf-8'), public_key)  # 字符串转bytes,用公钥加密
encrypted_base64 = base64.b64encode(encrypted).decode('utf-8')  # 二进制密文转Base64编码(转字符串)
print("加密结果:", encrypted_base64)

# 解密流程
decrypted = rsa.decrypt(base64.b64decode(encrypted_base64), private_key).decode('utf-8')  # Base64解码→私钥解密→转字符串
print("解密结果:", decrypted)

七、RSA 公钥在网页源代码中的呈现方式

在爬虫逆向场景中,RSA公钥常以两种形式暴露在网页源码中:

1. Base64编码公钥

直接提供Base64格式的公钥,需要手动拼接PEM格式头尾

const publicKeyBase64 = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQAB';

// 拼接为PEM格式
const pemPublicKey = `-----BEGIN PUBLIC KEY-----\n${publicKeyBase64}\n-----END PUBLIC KEY-----`;
const rsa = new NodeRSA(pemPublicKey);

可直接运行的完整代码示例:

// 1. 导入依赖
const NodeRSA = require('node-rsa');

// 2. Base64格式的RSA公钥
const publicKeyBase64 = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQAB';

// 3. 拼接为标准PEM格式公钥
const pemPublicKey = `-----BEGIN PUBLIC KEY-----\n${publicKeyBase64.match(/.{1,64}/g).join('\n')}\n-----END PUBLIC KEY-----`;

// 4. 构建RSA公钥实例(补充关键配置,解决兼容性/乱码)
const rsa = new NodeRSA(pemPublicKey, {
  encryptionScheme: 'pkcs1', // 兼容主流RSA实现(必加)
  encoding: 'utf8' // 避免中文加密乱码(必加)
});

// 5. 测试加密(验证公钥是否有效)
const plainText = "测试PEM格式公钥加密";
try {
  // 加密并输出Base64格式(易传输/存储)
  const encryptedBase64 = rsa.encrypt(plainText, 'base64');
  console.log("✅ 加密成功,结果(Base64):");
  console.log(encryptedBase64);
} catch (error) {
  console.error("❌ 加密失败:", error.message);
}
✅ 加密成功,结果(Base64):
o7Ir1BLfKQv9fSBBaGgbY1T/UeeoJaoq43GvD6LuY6SrtqW0IZgIEawMdc6dooVJeDnK+4dF+C7zwyGoEAt5qMwO9OFWDJ8OLP3uJ8j6p6voUsLS8bdzmLcHPH6Nmvj0oeSqaP4uIQlm8qIDJpyL1rQlzcRU7H3xUUWwSa8BqHs=

2. 仅提供ne

部分网站只暴露公钥的两个核心参数ne需要手动构建公钥对象

const NodeRSA = require('node-rsa');

let n_hex = "A8D534F67E3B8A72E8F1EAB634E457C10CA6F29E387267414FAAB8B47B62597F5C9FC3818485EAF529C26246F0155064A37867230CE2E5526EBBAC167702CD6050ED128D5127AC99044C4686556F5FF78FAC919E95B56FB9BB4F651CB723EA7439D6CD48FAB08191E75B35F44E9D06B3090F24787BD4F4E199C02814C4";
let e_hex = "010001";

let n_bigint = BigInt('0x' + n_hex);
let e_bigint = BigInt('0x' + e_hex);

let key = new NodeRSA({ n: n_bigint, e: e_bigint }, 'components-public');

可直接运行的完整代码示例:

// 1. 导入node-rsa库(仅需导入一次)
const NodeRSA = require('node-rsa');

// 2. 手动指定RSA公钥的模数(n)和指数(e)(16进制字符串)
const n_hex = "A8D534F67E3B8A72E8F1EAB634E457C10CA6F29E387267414FAAB8B47B62597F5C9FC3818485EAF529C26246F0155064A37867230CE2E5526EBBAC167702CD6050ED128D5127AC99044C4686556F5FF78FAC919E95B56FB9BB4F651CB723EA7439D6CD48FAB08191E75B35F44E9D06B3090F24787BD4F4E199C02814C4";
const e_hex = "010001"; // 标准RSA公钥指数65537(16进制表示)

// 3. 将16进制字符串转为BigInt(node-rsa要求的格式)
const n = BigInt(`0x${n_hex}`);
const e = BigInt(`0x${e_hex}`);

// 4. 构建RSA公钥实例(指定公钥组件+编码+加密方案)
const rsa = new NodeRSA(
  { n, e }, // 手动传入公钥的模数和指数
  'components-public', // 标识这是通过组件构建的公钥
  {
    encryptionScheme: 'pkcs1', // 加密方案(兼容Python/Java等主流RSA实现)
    encoding: 'utf8' // 默认编码,避免中文乱码
  }
);

// 5. 测试加密功能(核心补充逻辑)
const text = "你好,RSA公钥加密!"; // 待加密的中文文本
try {
  // 加密并将结果转为Base64(可读性强,便于传输/存储)
  const encryptedData = rsa.encrypt(text, 'base64');
  console.log("加密结果(Base64):", encryptedData);
} catch (err) {
  console.error("加密失败:", err.message);
}
加密结果(Base64): hy0GtL2TMIC6BAoNOvez9G358AnZ9ihh9xZ7LugKz/TcBXIWh65oUpAdL0zxSXlVIeHId5HKxL3nw8bx4gYgjPLUR2Y1H3JBO+oWDqPlNPunntzK8+8pvZtKQGoDhpWNwgygaWshGa/0uo1WTHUvPlWleOUc7xut01xPHsqoTYNfSXhVA2f+q/84g9WdWrUSE8w+b3KHcpSp+2IrYpVx3PqfQrXoXdU9BQSYwnagA4izts3RU4K4yN66RQWrfkli3IQnFBpmP/N++e2fLzxVBAUzmWGhjZ/uvKQDCSEesKD0JAN3SeB7djxJxjN0EsaOtLqB8mkRF171Iol603dmmw==

参数

作用

n(模数)

RSA 公钥的核心大整数,由两个大质数相乘得到,你的值是 512 位长度

e(公钥指数)

固定为 65537(16 进制010001),RSA 标准配置,用于加密计算

components-public

告诉 NodeRSA:传入的{n,e}是公钥数学组件,不是完整的 PEM 格式公钥

BigInt

处理超大整数(512 位的n远超 Number 的取值范围,必须用 BigInt)

八、总结

  • RSA是现代密码学的基石:它通过非对称密钥设计,解决了对称加密的密钥分发难题。

  • 安全性源于数学难题:大数质因数分解的难度,保证了RSA在当前计算能力下的安全性。

  • 实际应用中是"配角":主要用于密钥交换和数字签名,大数据加密仍依赖AES等对称加密算法。

  • 逆向工程的重点:网页中常暴露公钥参数,掌握提取和重构方法是爬虫逆向的关键技能。