应使用 Web Crypto API 的 AES-GCM 模式加密后再存入 Storage;密钥须 256 位且不可落地,IV 每次随机 12 字节,加密结果需 Base64 编码并结构化存储(含 iv、data、salt),同时避免存储原始密码、JWT、API 密钥等高危数据。

Web Storage(localStorage/sessionStorage)本身不加密,所有存进去的数据都是明文,一旦页面存在 XSS 漏洞,攻击者一行 localStorage.getItem('token') 就能拿走敏感信息。真正安全的做法不是“要不要加密”,而是“怎么加密才不白加”——密钥管理、算法选型和存储结构,三者缺一不可。
用 Web Crypto API 而不是 crypto-js
crypto-js 是纯 JS 实现,性能低、易被逆向,且默认 AES-CBC 不带认证,无法防篡改。现代浏览器原生支持的 Web Crypto API 更可靠:
- 必须用 AES-GCM 模式:自动提供加密+完整性校验,避免手动拼接 IV 和 MAC 的常见错误
- 密钥长度固定为 256 位(32 字节),导入时要显式声明
length: 256 - IV 必须每次随机生成、长度严格为 12 字节(GCM 推荐值),绝不能复用或硬编码
- 调用前检查环境:
if (window.crypto && crypto.subtle),HTTP 页面会静默失败
密钥不能落地,更不能写死
前端没有绝对安全的密钥存储位置,所以密钥本身绝不该出现在 localStorage、URL、cookie 或源码里。实际可用的方案只有两种:
-
用户口令派生密钥(PBKDF2):用
deriveKey+ 随机 salt + ≥100,000 次迭代生成密钥;salt 可和密文一起 Base64 存入 localStorage,但主口令只在解密时由用户输入 -
临时会话密钥:登录后调用
generateKey创建一次性密钥,全程仅存在于内存变量中,页面关闭即销毁
加密后必须结构化存储
Web Crypto 返回的是 ArrayBuffer,不能直接塞进 localStorage。正确做法是:
- 把 IV、密文、salt(如有)打包成对象,再 JSON 序列化:
{ iv: "base64", data: "base64", salt: "base64" } - Base64 编码推荐用
btoa(String.fromCharCode(...))或更安全的 base64url(替换+//,去掉填充=) - 解密时反向操作:JSON 解析 → Base64 解码 → 构造 Uint8Array → 传给
decrypt()
别忘了根本性防护:哪些数据根本不该存
加密只是补救手段,不是万能盾牌。以下数据即使加密也应避免本地存储:
- 原始密码、JWT token、API 密钥——优先用 HttpOnly + Secure Cookie 交给后端管理
- 手机号、身份证号、银行卡号等强敏感字段——前端不该持有,应由服务端按需返回脱敏结果
- 长期有效的访问令牌——必须设过期时间,配合服务端黑名单机制

















