IndexedDB 加密完全指南:用 RxDB 在浏览器磁盘上实现字段级数据加密

发布时间:2026/9/20 1:51:29
IndexedDB 加密完全指南:用 RxDB 在浏览器磁盘上实现字段级数据加密 数据库NoSQL嵌入式数据库实时数据库【免费下载链接】rxdbThe local-first database that runs on every JS runtime and replicates with your existing backend - no vendor, no lock-in - https://rxdb.info/项目地址https://gitcode.com/gh_mirrors/rx/rxdb点击查看免费下载IndexedDB 默认以明文把数据写入用户磁盘同源隔离并不能抵御拥有文件级访问权限的攻击者。本文围绕 RxDB 在 IndexedDB 之上构建的加密方案展开先剖析浏览器原生 API 为什么无法加密数据、手工用 Web Crypto 加密会踩哪些坑再给出基于 RxDB 加密插件的完整落地步骤包装存储、在 JSON Schema 中标记encrypted字段、透明读写最后结合仓库源码与性能测试数据讲清楚加密的代价、插件选型、密码管理与 FAQ。读完你将能直接为自己的 IndexedDB 应用加上一套可运行的字段级加密方案。IndexedDB 默认是加密的吗不是。IndexedDB 没有静态加密encryption at rest能力。浏览器会把你的 object store 以明文形式写入用户磁盘上的数据库文件而且这个文件格式是公开、有文档可查的在 Chrome 中数据存放在 LevelDB 目录里在 Firefox 中存放在 SQLite 文件里。任何对用户配置文件目录有读取权限的人包括本机上的其他进程、恶意桌面程序、具有相应权限的浏览器扩展都可以直接打开文件读出你的全部记录。这一点常常让开发者意外因为 IndexedDB 用起来感觉很私密它运行在浏览器内部被限定在单个 origin 内其他网站无法通过浏览器读取你的数据。但同源隔离不是加密。它只阻止另一个网站通过浏览器读取你的数据对直接读取磁盘上的原始文件毫无办法。于是威胁模型变得非常简单攻击者获得机器级别的文件访问权被盗笔记本、共享电脑、恶意桌面进程、权限过大的浏览器扩展就能读取你写入 IndexedDB 的每一条未加密记录对于凭据、Token、健康数据、财务记录这类敏感信息这是不可接受的。为什么 IndexedDB 原生 API 无法帮你加密原生 IndexedDB API没有任何加密特性indexedDB.open()没有加密选项object store 上没有加密开关也没有任何回调让你插入一个加密算法。API 被设计成一个底层存储引擎加密被留给了上层的实现。这给你留下唯一的原生方案在调用store.put()之前用 Web Crypto API 自己加密值再在每次store.get()之后解密。它确实能用但有几个非常现实的痛点失去查询能力。一旦某个字段变成密文字符串IndexedDB 上的索引就完全失效。你无法让浏览器找出所有status等于active的文档因为磁盘上status是一串随机字节。所有过滤都只能把记录逐个加载出来、解密后手动判断。每次读写都要手写胶水代码。Web Crypto 是异步的返回ArrayBuffer。你必须自己管理密钥、初始化向量IV、与可存储字符串之间的编码转换并在浏览器里包住每一个访问点。漏掉一处就是明文泄露。只要有一条代码路径写入值时不走你的加密辅助函数这条记录就会以明文落在磁盘上而你往往要等有人读了文件才会发现。一个常见但错误的修复是把整个数据库 blob 当成一个字符串整体加密。这在记录很少时勉强能用一旦记录变多就彻底崩坏因为每次读都要解密全部数据每次写都要重新加密全部数据——完全不可扩展。RxDB 如何在 IndexedDB 之上加一层加密RxDBReactive Database是一个 local-first 的 NoSQL 数据库运行在 IndexedDB 以及其他多种存储之上。它的加密插件会包装任意 RxStorage被标记的字段在写入前加密、读取时解密。你的数据仍然存放在 IndexedDB 底层。切换存储引擎只是配置变化而不是重写应用——加密是对存储的一层包装wrapper同样的写法既适用于免费的 Dexie.js 存储也适用于高级的 IndexedDB RxStorage。密钥派生、加解密、编码全部由 RxDB 处理你只需要声明哪些字段是敏感的。从源码看这个包装名副其实wrappedKeyEncryptionCryptoJsStorage()返回的对象通过Object.assign({}, args.storage, ...)保留原存储的全部能力只重写createStorageInstance见 src/plugins/encryption-crypto-js/index.ts。创建存储实例时它会判断 schema 中是否有加密字段hasEncryption见 src/rx-storage-helper.ts如果没有加密字段就原样透传给底层存储如果有则用wrapRxStorageInstance()把写入/读取两个方向的转换函数注入进去。整个过程对上层 API 完全透明。第 1 步用加密包装 IndexedDB 存储加密插件接收你的普通存储返回一个加密后的存储。除此之外数据库的一切保持不变。import { createRxDatabase } from rxdb/plugins/core; import { wrappedKeyEncryptionCryptoJsStorage } from rxdb/plugins/encryption-crypto-js; import { getRxStorageDexie } from rxdb/plugins/storage-dexie; // 用加密插件包装 Dexie/IndexedDB 存储 const encryptedStorage wrappedKeyEncryptionCryptoJsStorage({ storage: getRxStorageDexie() // 底层仍写入 IndexedDB }); // 密码用于解密数据务必不要写死在源码里 const db await createRxDatabase({ name: mydatabase, storage: encryptedStorage, password: sudoLetMeIn });关于密码源码里有两个硬性约束见 src/plugins/encryption-crypto-js/index.ts必须是字符串否则抛出RxTypeError错误码EN1最短长度为 8 个字符MINIMUM_PASSWORD_LENGTH 8长度不足抛出RxError错误码EN2。对应地仓库的加密测试专门验证了密码不出现在Object.keys(db)、展开对象和JSON.stringify(db)中以及过短密码的错误对象里不包含密码本身见 test/unit/encryption.test.ts从测试层面保证了密码不会被日志、序列化等常见途径意外泄露。第 2 步在 Schema 中标记敏感字段在 JSON Schema 中用encrypted数组声明需要加密的字段。只有这些字段在磁盘上是密文。主键primary key以及任何需要查询的字段保持明文可读。await db.addCollections({ users: { schema: { version: 0, primaryKey: id, type: object, properties: { // 主键必须声明 maxLength id: { type: string, maxLength: 100 }, email: { type: string }, // 该字段在磁盘上以密文存储 secret: { type: string } }, required: [id, email], encrypted: [secret] } } });这里有几个值得展开的细节主键必须带maxLength。RxDB 需要用主键值做索引与排序主键永远不加密加密字段的类型关键字会被替换为{ type: string }。源码中创建底层存储实例前会克隆 schema、删除encrypted属性、把附件加密关掉并把每个加密路径的 schema 定义替换成{ type: string }——因为加密后落盘的就是一串密文字符串原来的properties、required、items、maxLength、enum等类型关键字对密文不再适用见 src/plugins/encryption-crypto-js/index.ts。这也是为什么加密字段带maxLength的用例能正常通过测试见 test/unit/encryption.test.ts支持嵌套路径dot-notation例如encrypted: [nested.secretScore]可以把嵌套对象里的单个数字字段加密读取后仍是数字类型而非字符串见 test/unit/encryption.test.ts父路径与子路径不能同时加密。一旦加密了父字段整个对象会作为一个字符串整体加密因此不能再单独加密其子路径例如[nested, nested.secret]是非法的。dev-mode 插件会在建库阶段抛出SC43错误见 src/plugins/dev-mode/check-schema.ts测试也覆盖了这一场景见 test/unit/encryption.test.ts。第 3 步读写时完全不用碰密文加密与解密发生在 RxDB 内部。你像平常一样插入和查询文档secret字段在代码里是明文在磁盘上是密文。await db.users.insert({ id: user1, email: aliceexample.com, secret: my private token }); // 按非加密字段查询 const doc await db.users.findOne({ selector: { email: aliceexample.com } }).exec(true); console.log(doc.secret); // my private token - 已自动为你解密加密与解密的具体过程在modifyToStorage/modifyFromStorage两个函数中写入时对每个加密路径取值、JSON.stringify后用 AES 加密成字符串再写回读取时对密文解密并JSON.parse还原见 src/plugins/encryption-crypto-js/index.ts。注意它加密的是JSON 序列化后的值所以数字、布尔值、嵌套对象都能无损还原。测试中覆盖了对象字段加密、长文本加密约 220KB 特殊字符与incrementalPatch增量更新后解密正确等场景见 test/unit/encryption.test.ts。需要牢记加密字段不能出现在查询选择器里因为磁盘上是密文。请用主键或非加密字段查询把敏感数据放在加密字段中。如果你确实需要按加密值查询可以把数据复制到非加密的 memory mapped storage 中再查询。加密的性能代价加密不是免费的。每次写入都要先加密被标记的字段再落入存储每次读取都要再解密一次。这是叠加在存储访问之上的额外 CPU 开销而 IndexedDB 本身已经够慢密文又给本就不低的成本加了码。仓库在 Memory RxStorage 上对两套插件做了基准测量数据见 docs-src/src/components/performance-data.ts单位为毫秒越低越好指标WebCrypto AES-CBCWebCrypto AES-GCMWebCrypto AES-CTRCryptoJStime-to-first-insert2.212.532.452.04insert-documents-50043.8351.1645.94262.7find-by-ids-3000111.83139.59130.18723.46serial-inserts-5052.5158.1849.5631.4serial-find-by-id-503.684.323.96—find-by-query106.1137.18127.24—从数据可以清楚看到批量插入insert-documents-500和按 ID 查找find-by-ids-3000上WebCrypto 插件大约比 crypto-js 快 5 倍左右文档插入整体快约 10 倍。这些数字你都可以在 RxDB 仓库中自行复现。开销主要来自以下几个方面你需要心中有数默认在主线程上加密。密文计算是 CPU 密集型的在主线程上加密大量文档可能阻塞渲染、导致 UI 卡顿。解决办法是把存储挪进 Worker 或 SharedWorker让加密远离主线程。使用 Worker 时不需要担心密码传递——密码在主线程创建数据库时设置会自动传给 worker 内的存储见 docs-src/docs/encryption.md。加密字段无法使用索引。磁盘上加密字段是一串随机密文浏览器无法在其上构建有效索引查询也无法对其过滤。任何需要查询的字段必须保持不加密过度加密字段会悄悄把查询变成全表扫描。每次写入都会重新加密整个字段。RxDB 把每个被标记字段作为一个字符串整体加密字段内部没有部分更新。如果你在加密字段里放了一个大对象或长文本即使只改了一个小属性整个值也会被完整重新加密。crypto-js会让打包体积变大。免费插件打包了 crypto-js 模块会增加你的 JS bundle。高级的encryption-web-crypto插件改用浏览器原生 API因此携带的代码更少。控制成本的习惯只加密真正敏感的字段而不是整个文档让加密字段保持小而精简把大的加密 blob 存成 attachments——附件只在显式获取时解密查询运行时不会解密重负载场景把 WebCrypto 插件放进 worker 里跑。如何选择加密插件RxDB 提供两个加密插件两者用完全相同的方式包装存储encryption-crypto-js免费插件基于 crypto-js 库的AES算法。到处都能跑是一个稳妥的默认选择。encryption-web-crypto基于原生 Web Crypto API 的高级插件。文档插入比 crypto-js 快约 10 倍且因为使用浏览器 API 而不是打包 npm 模块打包体积更小。如果加密作用于大量文档Web Crypto 插件更值得投入。IndexedDB 本身已经慢你不想再叠加一个慢密码算法。WebCrypto 插件还支持在password中显式选择算法AES-CTR | AES-CBC | AES-GCM三选一并以算法 密码对象的形式传入见 docs-src/docs/encryption.md。补充两个与加密插件搭配使用的细节加密附件如需加密 attachments 数据在 schema 的attachments属性中设置encrypted: true附件内容会用数据库密码加密见 docs-src/docs/encryption.md。对应源码中modifyToStorage会把附件二进制转 base64 后加密成字符串存入 Blob见 src/plugins/encryption-crypto-js/index.ts。Worker 内加密的特殊要求在 worker 中用加密包装 OPFS 这类存储时必须给 OPFS 设置usesRxDatabaseInWorker: true否则 OPFS 出于性能优化会返回原始 JSON 字符串而不是解析后的对象加密包装无法处理这些字符串并会抛错见 docs-src/docs/encryption.md。密码管理与更换RxDB 不规定你如何存储或获取加密密码只要求你在创建数据库时提供它见 docs-src/docs/encryption.md。你可以在应用启动时让用户输入密码也可以从后端获取密码——想撤销访问权时只需停止提供密码即可。数据库的密码无法直接更换。密码是按数据库设置的用不同密码打开已存在的数据库会直接报错。测试里有一个专门的用例先用密码 A 创建并关闭加密库再用密码 B 重开ensureNoStartupErrors会抛出 different password 错误最后用正确密码重开则成功见 test/unit/encryption.test.ts。更换密码只有两种可行路径用 storage migration plugin 把数据库状态迁移到一个全新数据库把随机生成的 meta-password 存进另一个 RxDatabase 的 local document 中用真正的用户密码加密这层 meta-password创建实际数据库前先读出它——只替换外层密码即可完成轮换。另外两点值得注意加密插件本身使用对称加密基于密码性能最佳但它不能单独做非对称加密。如果需要公钥/私钥体系建议用非对称密钥加密密码本身把加密后的密码与数据放在一起应用启动时用私钥解出密码再交给加密插件见 docs-src/docs/encryption.md。所有通过 collection 读取文档的路径看到的都是解密后的值因此 JSON dump 导出会包含明文形式的加密字段请把这类导出当作敏感数据处理见 docs-src/docs/encryption.md。常见问题FAQIndexedDB 默认在静态时加密吗不。IndexedDB 以明文把数据写入用户磁盘上的文件。同源隔离能阻止其他网站读取它但挡不住拥有文件级访问权限的人。要保护静态数据必须在写入前加密值——加密的 RxStorage 包装帮你完成了这件事。如何加密 IndexedDB 中的数据两种选择一是自己用 Web Crypto API 在每次写入前加密、每次读取后解密这会破坏查询能力且容易出错二是用 RxDB 跑在 IndexedDB 之上在 schema 中用encrypted标记敏感字段加密解密完全透明。参见上文RxDB 如何在 IndexedDB 之上加一层加密。可以查询 IndexedDB 的加密字段吗不可以。加密字段以密文存储索引毫无意义、选择器无法匹配。请用主键或非加密字段查询把敏感数据留在加密字段中。如需按加密值查询可先复制到非加密的 memory mapped storage。加密会拖慢 IndexedDB 吗会有一点因为每次写入要加密、每次读取要解密。成本取决于插件免费的crypto-js适合小数据量高级的encryption-web-crypto插入速度约快 10 倍。重负载场景可以把加密放进 Worker storage让它离开主线程。之后可以更换加密密码吗不能直接换。密码按数据库设置一次用不同密码打开库会抛错。要轮换密码用 storage migration plugin 把数据迁移到新库或者用一个随机 meta-password 包住用户密码、只更换外层。详见 加密文档。延伸阅读完整阅读 RxDB 加密插件文档从 RxDB Quickstart 开始上手构建学习原生 API 的 IndexedDB 教程了解原生 API 为何慢的 Slow IndexedDB对比各存储方案的 IndexedDB Alternative阅读加密实现源码 src/plugins/encryption-crypto-js/index.ts 与加密测试 test/unit/encryption.test.ts。赞分享数据库NoSQL嵌入式数据库实时数据库【免费下载链接】rxdbThe local-first database that runs on every JS runtime and replicates with your existing backend - no vendor, no lock-in - https://rxdb.info/项目地址https://gitcode.com/gh_mirrors/rx/rxdb点击查看免费下载相关推荐Adminer数据加密插件字段级加密与解密实现指南在当今数据安全日益重要的时代 Adminer数据加密插件 为数据库管理员提供了强大的字段级加密与解密功能。作为一款轻量级的Web数据库管理工具Adminer数据库数据库客户端后端FLAME_PyTorch与RingNet项目集成实战3D面部重建的完整工作流FLAME_PyTorch与RingNet项目集成实战3D面部重建的完整工作流 FLAME_PyTorch是基于PyTorch实现的3D面部模型能够高效进行人工智能计算机视觉图形学深度学习zhenxun_bot加密消息存储数据库字段级加密实现zhenxun_bot加密消息存储数据库字段级加密实现 在即时通讯应用开发中用户消息记录的安全存储一直是核心挑战。zhenxun_bot作为基于Nonebo即时通讯人工智能大模型AI Agent交互助手插件系统MCP 服务上一篇WPF中使用Vlc.DotNet视频播放控件的高效实现与优化下一篇Notepad--跨平台中文编码文本编辑器5 分钟跑起来创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询