localStorage数据加密重在合理防护而非绝对安全,仅防低阶嗅探,不防XSS;须先剔除敏感凭证、原始隐私信息等不该存储的数据,再对确需持久化的低敏UI配置加密,密钥不可硬编码,推荐AES-GCM并结构化存储。

localStorage 数据加密不是“加了就安全”,而是要在明确边界、控制风险的前提下做一层合理防护。真正能防住的,是低阶嗅探或误操作;防不住 XSS 的直接读取——所以设计重点不在“能不能破”,而在“值不值得破”和“破了有没有用”。
先筛掉不该进 localStorage 的数据
加密前必须做减法。以下数据即使加密也不该存在这里:
- 用户 access_token、refresh_token、JWT 等登录态凭证 → 改用 HttpOnly + Secure Cookie,JS 完全不可读
- 原始密码、身份证号、银行卡号、手机号 → 前端根本不该持有,服务端按需返回脱敏结果
- API 密钥、内部服务地址、灰度开关配置 → 应由后端动态下发,或构建时注入环境变量
只对确需本地持久化的低敏配置加密
典型场景:用户主动设置且跨会话生效的 UI 配置,比如深色模式开关、语言偏好、默认排序方式、折叠面板状态等。这类数据明文存也无大碍,但若团队有合规要求或想统一防护水位,可加密:
- 密钥不硬编码:拼接生成(如
process.env.VUE_APP_KEY + new Date().getFullYear()),开发环境关闭加密便于调试 - 对象先
JSON.stringify(),解密后手动JSON.parse(),避免null/undefined导致解析失败 - 用 AES-GCM(非 CryptoJS 默认的 CBC):自带完整性校验,防篡改;IV 每次随机 12 字节,绝不复用
密钥管理比算法更重要
前端没有安全存密钥的地方,所以密钥本身不能落地:
- 用户口令派生:适合需手动输入密码才解密的场景(如密码管理器),用 PBKDF2 + salt + ≥100,000 次迭代,salt 可随密文存入 localStorage
- 会话级临时密钥:登录成功后调用
crypto.subtle.generateKey('AES-GCM', true, ['encrypt', 'decrypt']),密钥仅驻留内存,页面关闭即销毁 - 绝不用字符串写死密钥,也不从 localStorage 或 URL 中读取密钥
加密后结构化存储,别丢关键字段
Web Crypto API 返回的是 ArrayBuffer,不能直接塞进 localStorage。正确做法是打包再序列化:
- 加密结果包含三部分:iv(Base64)、data(Base64)、salt(如有,Base64)
- 存为标准 JSON 对象:
{ iv: 'xxx', data: 'yyy', salt: 'zzz' },再localStorage.setItem(key, JSON.stringify(obj)) - 解密时先
JSON.parse(),再 Base64 解码各字段,构造Uint8Array传给decrypt()


















