🔧 ToolForge — 干净、快速、免费的在线开发工具

2 阅读4分钟

前端也能搞加密?Web Crypto API 实战:AES + RSA 浏览器端加解密

不用后端,不用 openssl,不用装任何库。浏览器自带的 Web Crypto API 就能搞定 AES-256-GCM 和 RSA-OAEP。


为什么在浏览器里做加密?

场景很常见:

  • 前端传敏感数据给后端,传输层之前就想先加密一层
  • 做一个工具给非技术人员用,不想让他们装 openssl
  • 纯前端工具站,所有计算必须本地完成

以前前端做加密是噩梦——要么用 crypto-js(纯 JS 实现,慢),要么调后端 API(数据已经离岸了)。但 2026 年了,window.crypto.subtle 已经是所有现代浏览器的标配,直接用就行。

我自己做了一个免费在线工具站 ToolForge,其中 AESRSA 两个加密工具就是用 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 如果传错了(比如把 rawderiveKeyname 搞混),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


三、安全声明

如果你做的是一个给别人用的工具,必须让用户知道这三件事

  1. 所有运算在浏览器本地完成
  2. 密钥/密码不会离开用户的设备
  3. 不会上传任何数据到服务器

这不只是法律合规问题——用户看到"密钥上传"这个暗示就会关掉页面。


四、想试试?

不用自己写,直接体验:

全部开源,代码可以直接看:toolforge.cn


五、总结

AES-GCMRSA-OAEP
类型对称加密非对称加密
密钥一个密钥,加密解密都用它公钥加密,私钥解密
速度慢(非对称算法通病)
适用场景大量数据加密密钥交换、数字签名
明文上限无限制190 字节(2048 位密钥)
浏览器 APIcrypto.subtle.encrypt同左,换个算法名

Web Crypto API 让前端加密不再是玩具。只要注意密钥派生、IV 随机性和输出格式的自包含性,就能写出工程上能用的加密工具。


如果你有其他好用的在线工具推荐,评论区见 👇