前端也能搞加密?Web Crypto API 实战:AES + RSA 浏览器端加解密
不用后端,不用 openssl,不用装任何库。浏览器自带的 Web Crypto API 就能搞定 AES-256-GCM 和 RSA-OAEP。
为什么在浏览器里做加密?
场景很常见:
- 前端传敏感数据给后端,传输层之前就想先加密一层
- 做一个工具给非技术人员用,不想让他们装 openssl
- 纯前端工具站,所有计算必须本地完成
以前前端做加密是噩梦——要么用 crypto-js(纯 JS 实现,慢),要么调后端 API(数据已经离岸了)。但 2026 年了,window.crypto.subtle 已经是所有现代浏览器的标配,直接用就行。
我自己做了一个免费在线工具站 ToolForge,其中 AES 和 RSA 两个加密工具就是用 Web Crypto API 实现的。这篇文章拆解核心实现原理。
一、AES-256-GCM 加密
AES-GCM 是目前最推荐的对称加密方案——同时提供机密性和完整性校验。256 位密钥,GCM 模式自带认证标签,篡改的密文解密时会直接报错。
核心代码
// 1. 从用户密码派生 256 位密钥(PBKDF2 + 10 万次迭代)
async function deriveKey(password, salt) {
const enc = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
"raw", enc.encode(password), "PBKDF2", false, ["deriveKey"]
);
return crypto.subtle.deriveKey(
{ name: "PBKDF2", salt, iterations: 100000, hash: "SHA-256" },
keyMaterial,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
}
// 2. 加密
async function aesEncrypt(plaintext, password) {
const salt = crypto.getRandomValues(new Uint8Array(16)); // 随机盐
const iv = crypto.getRandomValues(new Uint8Array(12)); // GCM 推荐 12 字节 IV
const key = await deriveKey(password, salt);
const enc = new TextEncoder();
const ciphertext = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
enc.encode(plaintext)
);
// 输出格式:Base64(salt + iv + ciphertext)
return btoa(String.fromCharCode(...salt, ...iv, ...new Uint8Array(ciphertext)));
}
关键设计点
| 要素 | 选择 | 理由 |
|---|---|---|
| 密钥派生 | PBKDF2, 10 万次迭代 | 暴力破解门槛足够高 |
| IV | 随机生成,12 字节 | GCM 推荐的 IV 长度 |
| Salt | 随机生成,16 字节 | 防彩虹表攻击 |
| 输出格式 | Base64(Salt + IV + Ciphertext) | 自包含,解密时从前缀提取 |
解密
async function aesDecrypt(encoded, password) {
const raw = Uint8Array.from(atob(encoded), c => c.charCodeAt(0));
const salt = raw.slice(0, 16);
const iv = raw.slice(16, 28);
const ciphertext = raw.slice(28);
const key = await deriveKey(password, salt);
const decrypted = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv },
key,
ciphertext
);
return new TextDecoder().decode(decrypted);
}
一个容易踩的坑:deriveKey 的第二个参数 keyMaterial 如果传错了(比如把 raw 和 deriveKey 的 name 搞混),encrypt 阶段不会报错但解密出来的东西是天书。正确顺序:importKey(raw) → deriveKey(PBKDF2) → encrypt/decrypt(AES-GCM)。
二、RSA-OAEP 加密
RSA 是非对称加密——用公钥加密,私钥解密。OAEP(Optimal Asymmetric Encryption Padding)是推荐的填充方案,比旧式 PKCS#1 v1.5 更安全。
生成密钥对
async function generateRSAKeyPair() {
return crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048, // 2048 位(够用且性能可接受)
publicExponent: new Uint8Array([1, 0, 1]), // 65537
hash: "SHA-256"
},
true, // 密钥可导出
["encrypt", "decrypt"]
);
}
注意:modulusLength: 2048 意味着最多加密 190 字节(2048/8 - 2×32 - 2 = 190)。超过这个长度必须分段加密,或者用 RSA 加密一个 AES 密钥,再用 AES 加密数据(混合加密方案)。
公钥加密 / 私钥解密
async function rsaEncrypt(plaintext, publicKey) {
const enc = new TextEncoder();
const ciphertext = await crypto.subtle.encrypt(
{ name: "RSA-OAEP" },
publicKey,
enc.encode(plaintext)
);
return btoa(String.fromCharCode(...new Uint8Array(ciphertext)));
}
async function rsaDecrypt(encoded, privateKey) {
const raw = Uint8Array.from(atob(encoded), c => c.charCodeAt(0));
const decrypted = await crypto.subtle.decrypt(
{ name: "RSA-OAEP" },
privateKey,
raw
);
return new TextDecoder().decode(decrypted);
}
导出密钥(PEM 格式)
async function exportPublicKey(key) {
const spki = await crypto.subtle.exportKey("spki", key);
const body = btoa(String.fromCharCode(...new Uint8Array(spki)));
return `-----BEGIN PUBLIC KEY-----\n${body}\n-----END PUBLIC KEY-----`;
}
私钥用 "pkcs8" 格式导出,PEM 头是 BEGIN PRIVATE KEY。
三、安全声明
如果你做的是一个给别人用的工具,必须让用户知道这三件事:
- 所有运算在浏览器本地完成
- 密钥/密码不会离开用户的设备
- 不会上传任何数据到服务器
这不只是法律合规问题——用户看到"密钥上传"这个暗示就会关掉页面。
四、想试试?
不用自己写,直接体验:
- 🔒 ToolForge AES 加解密 — 在线 AES-256-GCM 加密,密码派生密钥,全浏览器本地计算
- 🔑 ToolForge RSA 加解密 — 在线 RSA-OAEP,一键生成密钥对,支持 PEM 导出
全部开源,代码可以直接看:toolforge.cn。
五、总结
| AES-GCM | RSA-OAEP | |
|---|---|---|
| 类型 | 对称加密 | 非对称加密 |
| 密钥 | 一个密钥,加密解密都用它 | 公钥加密,私钥解密 |
| 速度 | 快 | 慢(非对称算法通病) |
| 适用场景 | 大量数据加密 | 密钥交换、数字签名 |
| 明文上限 | 无限制 | 190 字节(2048 位密钥) |
| 浏览器 API | crypto.subtle.encrypt | 同左,换个算法名 |
Web Crypto API 让前端加密不再是玩具。只要注意密钥派生、IV 随机性和输出格式的自包含性,就能写出工程上能用的加密工具。
如果你有其他好用的在线工具推荐,评论区见 👇