写过前端加密的人都踩过这个坑——每次要用加密都要 npm install 一个库,今天浏览器把这个心脏彻底原生化了

写过前端加密功能的人都踩过这个坑——每次要用 AES-GCM,第一反应是 npm install crypto-js;每次要做 HMAC 签名,习惯性地去翻 bcryptjs;每次要跑 PBKDF2,直接顺手就 npm install 一个库。今天,浏览器自己把这个心脏彻底原生化了。

Web Crypto API(crypto.subtle)是 W3C 标准,从 Chrome 37(2014 年)、Firefox 34(2015 年)就开始内置,支持率覆盖全球 98%+ 的浏览器。但大多数前端工程师根本不知道它存在——直到今天。

你可能正在多装一个库

随便打开一个涉及加密的前端项目,package.json 里很可能会出现这些名字:crypto-jsjsrsasignbcryptjstweetnacl,或者 libsodium。这些库解决的是真实问题,但它们解决的那个问题,浏览器早就免费给你准备好了。

Web Crypto API 支持的算法:

  • 对称加密:AES-GCM(推荐)、AES-CBC、AES-CTR
  • 非对称加密:RSA-OAEP、RSA-PSS
  • 签名:HMAC、ECDSA、Ed25519(Chrome 113+/Firefox 130+)
  • 密钥交换:ECDH、X25519(Chrome 111+/Firefox 130+)
  • 密钥派生:PBKDF2、HKDF
  • 哈希:SHA-1、SHA-256、SHA-384、SHA-512

一个 crypto.subtle 全搞定,不需要任何外部依赖。

速度是硬件在跑,不是 JavaScript

用纯 JS 跑加密和用浏览器原生 API 跑加密,性能差距不是一个量级。

crypto-js 这类库的加解密全部跑在 JavaScript 引擎里。而 Web Crypto API 直接调用 CPU 的硬件指令集——AES-NI、硬件随机数生成器、专用加密运算单元。这意味着:

  • AES-256-GCM 加密:硬件加速,延迟接近零
  • RSA-4096 密钥生成:Native 代码,毫秒级完成
  • PBKDF2(25 万次迭代):硬件加速,比纯 JS 快 10-100 倍

这不是夸张。Web Crypto API 的加解密速度通常比纯 JS 实现快一到两个数量级,而且没有打包体积、没有第三方依赖、没有供应链风险。

怎么用:四步跑通 AES-GCM

以一个常见场景——用用户密码加密本地数据为例:

第一步:从密码派生密钥

const enc = new TextEncoder();

const keyMaterial = await crypto.subtle.importKey(
“raw”,
enc.encode(“user-password”),
“PBKDF2”,
false,
[“deriveKey”]
);

const key = await crypto.subtle.deriveKey(
{ name: “PBKDF2”, salt, iterations: 250000, hash: “SHA-256” },
keyMaterial,
{ name: “AES-GCM”, length: 256 },
false,
[“encrypt”, “decrypt”]
);

第二步:生成随机 IV 并加密

const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(12)); // 每次加密必须用新的

const ciphertext = await crypto.subtle.encrypt(
{ name: “AES-GCM”, iv },
key,
enc.encode(JSON.stringify({ token: “abc123”, userId: 42 }))
);

第三步:打包存储

// salt || iv || ciphertext 拼接后 base64 存储
const result = new Uint8Array(salt.length + iv.length + ciphertext.byteLength);
result.set(salt, 0);
result.set(iv, salt.length);
result.set(new Uint8Array(ciphertext), salt.length + iv.length);
return btoa(String.fromCharCode(…result));

第四步:解密还原

const decrypted = await crypto.subtle.decrypt(
{ name: “AES-GCM”, iv },
key,
ciphertext
);
return JSON.parse(new TextDecoder().decode(decrypted));

这个流程里,PBKDF2 的 250000 次迭代是 OWASP 2023+ 推荐基准,用来对抗暴力破解。密钥派生完成后,extractable: false 意味着 JavaScript 永远读不到原始密钥材料——即使页面被 XSS 注入,攻击者也拿不走密钥。

三个实际场景

  1. 零知识存储

加密数据在发送到服务器之前就在浏览器里完成加密,服务器存储的只有密文。即使服务器被拖库,用户数据仍然是加密的。这是端到端加密在 Web 端的实现方式——Telegram Web、Signal Web 版用的就是这套方案。

  1. 文件加密

用户上传文件前,先在浏览器里加密,再上传密文:

const fileBuffer = await file.arrayBuffer();
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: “AES-GCM”, iv },
key,
fileBuffer
);

服务端永远不接触明文,特别适合医疗、金融类应用。

  1. 签名与验签

HMAC 签名用于验证请求来源和完整性:

const signingKey = await crypto.subtle.importKey(
“raw”,
enc.encode(“secret”),
{ name: “HMAC”, hash: “SHA-256” },
false,
[“sign”]
);
const sig = await crypto.subtle.sign(“HMAC”, signingKey, enc.encode(message));
// 验证用 crypto.subtle.verify(“HMAC”, signingKey, sig, enc.encode(message))

和 Rust 工具链的连接点

Rust 工程师对这个工作流应该非常熟悉——AES-GCM + PBKDF2 + ECDH 这套组合拳,正是 Rust 生态里 ringx25519-daleked25519-dalek 这些 crate 做的事。Web Crypto API 做的事本质上是一样的,只是把接口从 Rust API 搬到了浏览器里。

Rust 工程师在前端工具链里推动的现代加密标准(如更安全的密钥派生参数、更强的算法),最终也会沉淀到 Web Crypto API 里。

坑和注意事项

  • 所有操作都是异步的:Web Crypto API 返回 Promise,不接受回调。
  • 数据类型是 ArrayBuffer/TypedArray:你得习惯在 Uint8Array 和字符串之间来回转。
  • IV 绝对不能复用:AES-GCM 下,每次加密必须生成新的随机 IV。
  • Ed25519 支持较新:Firefox 130+ 才稳定,移动端 Safari 17+。
  • 必须 HTTPS:Web Crypto API 在非安全上下文下会被禁用(localhost 除外)。

下一步

现在去你的 package.json 里搜一下 cryptobcrypttweetnacljsrsasign。如果它们只用来做 AES/HMAC/PBKDF2 这类基础操作,那第一步就是把这些依赖删掉,换成三行代码。

浏览器已经免费把这件事做了八年了。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 11 阅读