如何用javascript实现加密和解密_为什么Web Crypto API适合安全操作

Web Crypto API 是当前最可靠、浏览器原生支持的前端加密方案。它由 W3C 标准化,运行于安全上下文,密钥抽象为不可导出的 CryptoKey 对象,强制安全参数与异步操作,避免内存泄露和误用风险。

JavaScript 中实现加密和解密,Web Crypto API 是当前最可靠、浏览器原生支持的方案。它由 W3C 标准化,运行在安全上下文(HTTPS 或 localhost)中,密钥不会暴露在 JavaScript 内存里,避免了传统库(如 CryptoJS)手动实现易出错、缺乏密钥隔离等风险。

为什么不用第三方加密库?

像 CryptoJS、SJCL 这类纯 JS 实现的库,虽然用法简单,但存在明显短板:

  • 算法依赖开发者手动组合,容易误用(比如用 ECB 模式加密敏感数据)
  • 密钥以字符串或字节数组形式存在于 JS 堆内存,可能被恶意脚本读取或通过内存快照泄露
  • 不支持硬件级密钥保护(如 Secure Enclave、TPM),也无法与浏览器密码管理器、操作系统密钥链联动
  • 部分算法(如 PBKDF2、AES-GCM)虽有实现,但缺少标准的密钥派生参数校验或 AEAD 认证逻辑封装

Web Crypto API 的核心优势

它不是“另一个加密库”,而是浏览器提供的安全子系统接口:

  • 密钥抽象化:生成的密钥是 CryptoKey 对象,不可序列化为字符串,不能被 console.log 或 JSON.stringify 导出
  • 上下文隔离:仅在 HTTPS 页面或 localhost 可用,防止中间人篡改脚本后窃取密钥操作逻辑
  • 算法白名单+强制安全参数:例如 AES-GCM 要求显式传入 iv 和 tagLength,无法跳过认证步骤;importKey 对非安全导入(如 JWK)可设 extractable: false
  • 异步 + Promise 原生支持:所有操作返回 Promise,避免阻塞主线程,适合处理大文件或多次派生

一个安全的 AES-GCM 加解密示例

以下代码演示如何用 Web Crypto API 完成一次端到端加密(含密钥派生和认证):

// 1. 从口令派生加密密钥(PBKDF2 + salt)
async function deriveKey(password, salt) {
  const enc = new TextEncoder();
  const keyMaterial = await window.crypto.subtle.importKey(
    'raw', enc.encode(password), { name: 'PBKDF2' }, false, ['deriveKey']
  );
  return window.crypto.subtle.deriveKey(
    { name: 'PBKDF2', salt, iterations: 100_000, hash: 'SHA-256' },
    keyMaterial,
    { name: 'AES-GCM', length: 256 },
    true,
    ['encrypt', 'decrypt']
  );
}

// 2. 加密(自动生成 IV,附带认证标签)
async function encrypt(plainText, password) {
  const salt = window.crypto.getRandomValues(new Uint8Array(16));
  const iv = window.crypto.getRandomValues(new Uint8Array(12));
  const key = await deriveKey(password, salt);

  const enc = new TextEncoder();
  const cipherData = await window.crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    key,
    enc.encode(plainText)
  );

  // 返回 salt + iv + ciphertext(base64 方便传输)
  return btoa(String.fromCharCode(...salt, ...iv, ...new Uint8Array(cipherData)));
}

// 3. 解密(验证完整性和密文一致性)
async function decrypt(encodedData, password) {
  const data = new Uint8Array(atob(encodedData).split('').map(c => c.charCodeAt(0)));
  const salt = data.slice(0, 16);
  const iv = data.slice(16, 28);
  const cipherText = data.slice(28);

  const key = await deriveKey(password, salt);
  const plainBytes = await window.crypto.subtle.decrypt(
    { name: 'AES-GCM', iv },
    key,
    cipherText
  );

  return new TextDecoder().decode(plainBytes);
}

注意:实际使用中需校验 encodedData 长度、IV 是否为 12 字节、salt 是否存在——这些不是 API 强制的,但属于必要安全检查。

什么场景下仍要谨慎?

Web Crypto API 本身安全,但整体安全性取决于你的使用方式:

  • 口令强度弱?再好的派生也扛不住暴力破解——应配合前端口令强度提示和后端限速
  • 把密钥或口令存在 localStorage?这等于把钥匙挂在门把手上——密钥应只驻留内存,且用完即弃
  • 在非安全上下文(HTTP)调用?API 直接抛错——确保部署时启用 HTTPS
  • 需要长期存储密钥?考虑 navigator.credentials.create() 创建凭据,或与 WebAuthn 结合做用户绑定

Web Crypto API 不是万能钥匙,但它把加密操作从“自己造轮子”变成“调用安全基础设施”。只要理解密钥生命周期、坚持 AEAD 模式、避免明文落地,就能在前端构建真正可信的加解密能力。