localstorage 不加密,xss 下敏感数据易泄露;必须用 web crypto api + aes-gcm 加密,密钥须口令派生或临时生成,iv 随机且 12 字节,加密后结构化存储为 base64 json。

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











