真正有效的做法是先判断配置项是否必须存本地,再决定是否加密及方式;必须存的仅限用户主动设置且需跨会话持久化的纯UI配置,敏感数据如token应禁用localStorage而改用httpOnly Cookie。

直接在 localStorage 里存加密数据,并不能真正提升敏感配置项的安全性——因为密钥管理、运行环境和攻击面都在前端可控范围之外。真正有效的做法是:先判断这配置项是否真的该存在本地,再决定要不要加解密,以及怎么加。
第一步:确认“必须存本地”的合理性
很多所谓“本地配置”其实是伪需求。比如 API 地址、功能开关、灰度标识等,完全可由后端动态下发,或通过环境变量注入构建时。存进 localStorage 的唯一合理场景是:用户主动设置且需跨会话持久化(如深色模式、语言偏好、折叠面板状态)。一旦涉及账号、token、密钥、设备指纹等,就不该出现在 localStorage 中。
- 若配置含 token 或临时凭证,应改用 httpOnly + Secure Cookie 存储,并配合短时效与服务端校验
- 若为调试开关或灰度标识,可用简单混淆(如 base64 + 键名倒序)替代加密,降低维护成本
- 若用户自定义主题色等纯 UI 配置,明文存储也无风险,HTTPS + CSP 已足够防护
第二步:基础防护不到位,加密等于摆设
就算你用了 AES-GCM,如果页面本身不强制 HTTPS、CSP 放宽到 unsafe-eval、或 JS 被中间人篡改,攻击者几秒就能拿到解密后的明文或直接 hook 解密函数。所以加密前必须确保:
- 全站启用 HTTPS,所有资源(JS/CSS/iframe/fetch)都走 https:// 协议
- CSP 头禁用 unsafe-inline、unsafe-eval,限制 script-src 只允许可信域名
- 避免在 HTML 中内联脚本,加密逻辑统一收口到独立的、带 SRI 校验的 JS 文件中
第三步:真要加密,选对方式和边界
加密不是目的,防止未授权读取才是目标。不同场景适用不同策略:
立即学习“前端免费学习笔记(深入)”;
- 用户口令派生密钥(PBKDF2 + salt):适合用户明确输入密码后才解密的场景(如密码管理器),每次读取都要输密码,不适合自动恢复的配置项
- 服务端下发一次性密钥:登录成功后,后端用 RSA 公钥加密一个 AES 密钥返回,前端用它加解密 localStorage 数据;密钥不落地、不复用,但需配套签名验证与过期机制
- CryptoJS.AES.encrypt(仅限低敏):兼容老浏览器,但必须满足:密钥不硬编码、salt 和 IV 随机生成并随密文一并存储、对象先 JSON.stringify 再加密、解密后手动 JSON.parse
第四步:存取流程要闭环,别漏关键细节
以 CryptoJS 为例,常见错误是把 CipherParams 对象直接塞进 localStorage。正确写法是:
- 加密时:
localStorage.setItem('cfg', CryptoJS.AES.encrypt(JSON.stringify(obj), key, { iv: iv }).toString()) - 解密时:
const bytes = CryptoJS.AES.decrypt(localStorage.getItem('cfg'), key, { iv: iv }); const obj = JSON.parse(bytes.toString(CryptoJS.enc.Utf8)) - 中文字段务必用
encodeURIComponent包裹后再 JSON.stringify,否则 atob/btoa 会出错 - 不要用 window.btoa + encodeURI 模拟“加密”,这只是编码,毫无安全意义
本质上,localStorage 加密是种权衡:它防不了懂技术的用户,也挡不住 XSS 或中间人,只对粗粒度的数据窥探有一点延缓作用。重点永远在“不该存的坚决不存”,而不是“存了就拼命加密”。



















