systemslibrarian/crypto-lab-search-vault

GitHub: systemslibrarian/crypto-lab-search-vault

一个在浏览器中演示可搜索对称加密(SSE)原理及其访问模式泄漏风险的教学项目,包含真实密码学原语实现的加密索引构建、搜索查询和泄漏滥用攻击复原。

Stars: 0 | Forks: 0

# 搜索 Vault **可搜索对称加密 · 加密关键字搜索** 一种可搜索加密技术,服务器可以在不知道查询内容或数据的情况下,在加密数据库中找到匹配的记录——但它观察到的访问模式本身就是一个攻击面。 **在线演示:** https://systemslibrarian.github.io/crypto-lab-search-vault/ ## 它是什么 一个加密的倒排索引,在你的浏览器中构建和搜索,外加已公开的、能将其泄漏信息还原为你原始查询的攻击方法。 该方案是一个单关键字 SSE 构造,采用 Song–Wagner–Perrig (2000) / Curtmola–Garay–Kamara–Ostrovsky (SSE-1, 2006) 风格,全程使用真实的密码学原语: | 数值 | 派生方式 | 原语 | | --- | --- | --- | | 搜索 token `t_w` | `HMAC(K_prf, "token" ‖ w)` | HMAC-SHA-256 (RFC 2104 / FIPS 198-1) | | 倒排列表 key `k_w` | `HMAC(K_enc, "posting" ‖ w)` | HMAC-SHA-256 | | 索引地址 `addr_i` | `HMAC(t_w, "address" ‖ i)` | HMAC-SHA-256 | | 倒排列表值 | `AES-256-GCM(k_w, docId)`, AAD = `addr_i` | AES-256-GCM (NIST SP 800-38D) | | 文档正文 | `AES-256-GCM(k_d, title ‖ body)` | AES-256-GCM | 所有这些都通过 WebCrypto 运行。密钥通过 `crypto.getRandomValues` 在每次会话中生成,保存在内存中,且从不持久化或传输。 **安全模型。** 服务器是*诚实但好奇的*:它遵循协议并记录它看到的一切。它无法逆推出 token(它不持有任何密钥),也无法读取倒排列表或文档(AES-GCM)。按照设计,它确实能看到的是**泄漏轮廓**: - **搜索模式** — token 是确定性的,因此重复查询是可关联的; - **数量** — 一个 token 匹配了多少文档; - **访问模式** — 确切是哪些文档匹配了,因为它必须把这些文档交出来。 第三点正是本演示的主题。泄漏滥用攻击(Islam–Kuzu–Kantarcioglu, NDSS 2012; Cash–Grubbs–Perry–Ristenpart, CCS 2015)在已知类似语料库背景统计信息的情况下,仅凭模式即可重构查询。这两种攻击都在此实现,并针对你使用页面时实际累积的日志运行。 **不是生产级加密 —— 这是一个教学演示。** 语料库特意设置得很小(24 个文档,14 个关键字),以便攻击在你观看时完成,索引是静态的,且“客户端”和“服务器”是同一页面中的两个对象,而不是两台主机。 ## 展示区 1. **构建加密索引** — 逐步展示一个关键字如何变成一个加密的倒排索引,每次进行一次真实的 PRF 调用:关键字 → token → 倒排列表 key → 地址 → 加密封装值 → 一行数据落入服务器的存储中。显示的十六进制是保险库自身的真实字节,而非模拟重演。在它旁边是服务器的完整存储:55 行数据,没有任何信息说明哪些行属于同一个关键字。 2. **搜索它 —— 并观察服务器学到了什么** — 运行一次真实的查询并同时查看两端。客户端列显示关键字和解密后的结果;服务器列显示它接收到的 32 字节,它执行的地址遍历(`addr_0`、`addr_1`、…… 直到未命中),以及它返回的文档标识符。一个持续运行的日志记录了每一次查询。搜索未被索引的关键字会显示未命中的路径。 3. **单凭模式揭示了什么** — 仅根据日志构建的三个视图:查询频率直方图(搜索模式)、结果集重叠图(访问模式)和文档共现矩阵。以及出现在某个结果集中的数据库比例。 4. **破解它:将访问模式还原为查询** — 计数攻击会锁定任何在关键字全域中结果大小唯一的 token;IKK 攻击通过模拟退火将观察到的 token 共现矩阵与已知语料库的矩阵进行匹配。一个滑块会降低对手的背景知识水平,让你观察恢复率的下降过程。每个 token 的表格报告了对手的推断、其置信度以及该 token 实际代表的含义。 5. **轮到你了:你是服务器** — 你将获得 token、它们的结果大小、它们的重叠情况,以及攻击所获得的相同公开统计数据。手动为 token 分配关键字,为自己打分,然后让机器尝试解开同一个谜题。 6. **SSE、ORAM、FHE** — 它们各自隐藏了什么以及各自的代价。SSE 延迟列是在你的浏览器中针对此保险库测量的;ORAM 和 FHE 列被标记为引用的、数量级估计的数值,因为本页面两者都不运行。 此外还有一个**范围**面板,说明了哪些是真实的,哪些是缩减规模的,这不能证明什么,以及什么是故意超出范围的。 ## 何时使用它 **在以下情况使用可搜索对称加密:** - 你必须搜索一个大型的加密存储,并且每次查询的成本必须随*匹配项*的数量成比例增长,而不是随数据库的大小增长; - 你关注的威胁是读取静态数据的被动主机,或存储系统的违规泄露; - 你能容忍服务器知道哪些记录匹配了查询,并且你已经考虑过这对你的数据意味着什么。 **在以下情况请勿使用:** - **查询本身是你必须防范存储提供商以保护的核心机密。** 这正是本演示旨在说明的情况:在拥有关于你语料库的背景知识的情况下,访问模式是可以被逆推的。如果你的查询是敏感的(记者的线人、患者的诊断代码),仅靠 SSE 是错误的工具——你需要 ORAM、PIR,或者干脆不外包索引。 - 你需要联合查询或排名查询,并假设泄漏情况没有改变。事实并非如此;越丰富的查询泄漏得越多。 - 你的索引在不断变化,而你尚未选择具有前向和后向隐私保护机制的方案。 - 你想尝试使用保序加密或确定性加密来让 SQL 正常工作。那比这种方式泄漏得多得多。 ## 在线演示 https://systemslibrarian.github.io/crypto-lab-search-vault/ 你可以:逐步查看 14 个关键字中任何一个的索引构建过程;搜索真实和不存在的关键字,看着服务器的日志被填满;观察一轮真实倾斜的查询;阅读三个泄漏视图;在任意级别的对手背景知识误差下运行这两种攻击;尝试自己将 token 去匿名化,并与机器的得分进行比较;并在你自己的浏览器中对 50 次真实搜索进行计时。 ## 可能出现的问题 - **访问模式泄漏不是旁枝末节。** 拥有精确的背景统计数据时,展示区 4 中的攻击能在几十毫秒内恢复全部 14 个查询。即使对手的统计数据退化到远远超过现实误差的水平,大多数 token 依然会被攻破。 - **仅凭数量泄漏就足以造成问题。** 任何文档计数在词汇表中唯一的关键字,都会在不进行任何共现分析的情况下,仅凭结果大小被识别出来。填充结果集是标准的应对措施,但这需要消耗存储空间和带宽。 - **确定性 token 随着时间推移将查询关联起来。** 服务器无法读取 token,但它能识别出每一次重复,因此它可以建立每个用户的查询频率画像,并将其与公开的词频数据进行比对。 - **背景知识假设比听起来要弱。** 对手只需要一个*类似*语料库的统计数据,而不是你的——泄露的档案、公开的文件以及同类组织都能提供这些信息。 - **静态索引隐藏着持续存在的危险。** 本演示只构建一次索引。除非方案具有明确的前向隐私保护,否则以后添加文档时会泄漏新文档是否匹配了之前的查询。 - **篡改会导致闭环失败,这是刻意为之。** 每个倒排列表都以其自身的地址作为关联数据进行封装,因此服务器一旦重新定位、伪造或翻转任何一行数据,都会导致整个结果被拒绝,而不是静默地给出错误答案。有六个测试覆盖了这条路径。 ## 实际应用 当全同态加密太慢时(这几乎是常态),可搜索加密便是得以部署的技术。它的各种变体出现在加密数据库产品、加密搜索设备,以及仍然提供搜索框的客户端加密笔记和邮件服务中。学术脉络贯穿 Song–Wagner–Perrig (2000) → Curtmola 等人的 SSE-CKA 安全定义 (2006) → Cash–Jarecki–Jutla–Krawczyk–Roşu–Steiner (2013) 的 OXT 联合查询方案 → 重塑了该领域预期的泄漏滥用攻击 (Islam–Kuzu–Kantarcioglu 2012, Cash–Grubbs–Perry–Ristenpart 2015) → 在其影响下设计的前向和后向隐私动态方案。 持久的教训正是本演示所围绕构建的核心:对于 SSE 来说,**泄漏轮廓即是安全模型。** 一个方案在抽象意义上不是“安全”或“不安全”的——它只有在*相对于既定的泄漏函数*时才谈得上安全,而这种泄漏是否可以接受,是一个关于你的数据和你的对手的问题,而不是关于密码学本身的问题。 ## 如何在本地运行 ``` npm install npm run dev # http://localhost:5173 npm test # unit tests, including the spec KATs npm run build # typecheck + production build npm run test:a11y # axe-core WCAG 2.1 AA gate against the production build ``` `npm run test:a11y` 会自动在 **4237** 端口启动 `vite preview`,因此扫描的内容就是最终发布的内容。 ## 相关演示 - [crypto-lab-oram-vault](https://systemslibrarian.github.io/crypto-lab-oram-vault/) — Path ORAM:隐藏本演示所泄漏的访问模式。 - [crypto-lab-psi-gate](https://systemslibrarian.github.io/crypto-lab-psi-gate/) — 私有集合求交:在不泄露非匹配元素的情况下对两个私有集合进行计算。 - [crypto-lab-fhe-arena](https://systemslibrarian.github.io/crypto-lab-fhe-arena/) — 全同态加密:同一权衡中代价高昂的一端。 ## 构建与验证 **77 个单元测试**,全部通过,由 `npm test` 运行并作为部署的门禁。 **11 个规范已知答案向量:** | 文件 | 向量 | | --- | --- | | `src/core/prf.test.ts` | 6 × HMAC-SHA-256,来自 RFC 4231 §4(测试用例 1, 2, 3, 4, 6, 7) | | `src/core/prf.test.ts` | 3 × SHA-256,来自 FIPS 180-4(空消息、`"abc"`、448 位双块消息) | | `src/core/aead.test.ts` | 2 × AES-256-GCM,来自 GCM 规范的向量集(测试用例 13 和 14),双向均已验证 | 除了 KAT 之外,测试套件还涵盖了:索引构建(每个关键字-文档对一个倒排列表,不同的伪随机地址,没有两个完全相同的密文);语料库中每个关键字的搜索正确性;未索引关键字和随机 token 的未命中路径;六个闭环失败案例(翻转的密文、被篡改的 IV、错误的密钥、截断的 blob、重新定位的倒排列表、伪造的倒排列表);服务器账本记录和未记录的内容;泄漏计算——包括一个类似证明的检查,验证观察到的共现矩阵与明文矩阵*完全一致*,以及一个颜色细化检查,验证语料库中没有两个关键字在信息论上是可互换的;以及攻击——在精确背景知识下的完全恢复、在噪声下的优雅降级、在固定种子下的确定性,以及对格式错误输入的拒绝。 **无障碍门禁:** `@axe-core/playwright` 在**两种主题**下通过四次扫描检查生产构建是否存在 WCAG 2.1 A/AA 违规——每个展示区都被驱动到其交互后的状态,以及作为每个条件渲染另一个分支的空/未命中状态。要求违规数为零;回归问题将阻止部署。 ## 性能 搜索速度很快,因为 SSE 让服务器能够进行普通的索引查找:工作量与匹配文档的数量成正比,而不是与数据库的大小成正比。展示区 6 对其进行了实时测量——50 次完整搜索,每次涵盖 token 派生、地址探测、AES-GCM 倒排列表解密和文档获取——并报告你的浏览器的单次搜索数据。在现代笔记本电脑上,它可以在低个位数的毫秒级内完成。 攻击的成本同样低廉:计数攻击是一次表查找,而 IKK 退火(8 次重启 × 12,000 次迭代,外加贪婪法润色)能在几十毫秒内解析出 14 个 token。攻击成本低廉正是问题的关键所在。 *这是 [Crypto Lab](https://crypto-lab.systemslibrarian.dev/) 套件中 170 多个浏览器演示之一。* *“所以,你们或吃或喝,无论做什么,都要为上帝的荣耀而行。” —— 哥林多前书 10:31*
标签:WebCrypto API, 前端加密, 可搜索加密, 密码学, 手动系统调用, 数据隐私, 特征检测, 自动化攻击