3. 可搜索加密基础:对称与非对称,索引构建与查询流程

聊到密文检索,很多人第一反应就是“把数据加密存起来,然后怎么查?” 嗯,这确实是核心问题。传统方法你得把数据全解密到内存里,再一个个比对——那加密还有什么意义? 可搜索加密(Searchable Encryption,SE)就是来解决这个矛盾的:在不泄露明文和查询内容的前提下,还能支持搜索。

我在做第一个审计日志项目的时候,踩过这个坑。当时我们直接把日志字段加密后扔进数据库,结果要查某条记录时,只能把全量数据拉下来解密。一次全表扫描就要十几分钟,线上根本扛不住。后来才老老实实上了可搜索加密。说白了,这个技术就是在“安全”和“可用”之间找一个平衡点。

这一节我们重点拆解两种主流方案:对称可搜索加密(SSE)和非对称可搜索加密(PEKS)。它们俩的思路不太一样,适用的场景也有明显区别。

3.1 对称可搜索加密(SSE)

SSE,全称是 Searchable Symmetric Encryption。它的核心假设是:加密和搜索用同一把密钥,或者至少搜索密钥能从加密密钥派生出来。这个场景非常常见——比如你在自己的服务器上加密存储数据,然后用自己持有的密钥去检索。

3.1.1 索引构建:正向索引与倒排索引

SSE 的索引,说白了就是给每个关键词建立一个“文档列表”的映射。我在项目里常用的方式是构建一个加密的倒排索引。怎么做的?大致分几步:

  1. 提取关键词:对每篇文档做分词,得到一组关键词 w₁, w₂, ..., wₙ。
  2. 加密文档标识:对每个文档 ID 用密钥加密,得到加密后的标识符 e_id。
  3. 建立映射表:对每个关键词 w,构建一个列表 L(w),里面存储所有包含 w 的加密文档标识 e_id。
  4. 加密索引:用主密钥对整个映射表加密,或者对每个关键词独立加密。

避坑指南:我个人习惯在构建索引时,对关键词做一次哈希处理,而不是直接暴露明文关键词。这样即使索引被偷走,攻击者也无法直接还原出原始词表。当然,哈希不能抵抗字典攻击,所以搭配盐值(salt)会更安全——我曾经在一个金融项目里因为忘了加盐,被安全审计揪出来过,很惨。

3.1.2 查询流程

SSE 的查询流程本质上就是一个“令牌交换”过程。客户端拿着搜索关键词,生成一个搜索令牌(),然后发给服务器。服务器拿着这个令牌在自己的加密索引里定位,返回匹配的加密文档列表。

我画了一个典型的 SSE 查询流程,你看一下:

// 伪代码:SSE 查询流程
// 客户端侧:生成搜索令牌
 = F(k, keyword)    // F 是伪随机函数,k 是密钥

// 客户端发  给服务器

// 服务器侧:查找加密索引
encrypted_doc_ids = lookup_index()   // 直接在加密索引上操作

// 如果匹配到,服务器返回 encrypted_doc_ids 列表
// 客户端用密钥解密得到原始文档 ID

这个流程有几个硬性特点:

  • 服务器全程看不到关键词明文,也看不到文档内容。
  • 查询的时间复杂度是 O(1) 或者 O(log n),取决于索引结构。我在实践中倾向于用哈希表加链接节点的结构,能做常数时间匹配。
  • 最大缺陷是:同一个关键词每次生成的令牌是相同的。这就意味着服务器能观察到哪些查询是重复的,存在访问模式泄露。

注意:SSE 的访问模式泄露在某些场景下可能是致命的。举个例子,如果攻击者知道你频繁查询某个关键词,结合一些外部信息就能推断出你的搜索意图。高级的安全方案会引入“混淆”或“填充”机制来抵抗这种攻击,但会牺牲性能。别指望一张完美的空头支票,安全总得有代价。

3.2 非对称可搜索加密(PEKS)

PEKS,全称是 Public Key Encryption with Keyword Search。和 SSE 不同,PEKS 使用公钥加密,私钥搜索。你可以把加密数据公开放在服务器上,但只有持有私钥的接收方才能发起可用的搜索令牌。

我第一次接触 PEKS 是在一个去中心化的审计日志系统里。多个审计源需要往同一个中心存储写入加密日志,但彼此之间不能共享密钥——这时候 SSE 就不太合适了,因为 SSE 要求所有加密方知道同一个对称密钥。而 PEKS 只要你把公钥公开给所有审计源,私钥只保留在审计管理员手里,完美解耦。

3.2.1 索引构建:公钥加密的关键词

PEKS 的索引构建和 SSE 不太一样。它不依赖“倒排索引”这种显式结构,而是为每个关键词独立生成一个可搜索密文。具体来说:

  1. 数据发送方拿到公钥 PK。
  2. 对于每个关键词 w,计算 PEKS(PK, w) → C,C 是一个密文。
  3. 把 C 和对应的加密文档一起存储。服务器不知道 C 对应哪个关键词。

这里有一个技术难点:PEKS 的密文长度通常是常数级的,不像 SSE 的索引会随着文档数量增长。但代价是计算开销比较大,因为涉及双线性对运算。

3.2.2 查询流程:陷门(Trapdoor)机制

查询流程和 SSE 刚好对称过来:这一次,令牌(在 PEKS 里叫“陷门”)由接收方生成,服务器负责测试匹配。

// 伪代码:PEKS 查询流程
// 接收方(持有私钥 SK):生成陷门
trapdoor = Trapdoor(SK, keyword_search)

// 接收方发送 trapdoor 给服务器

// 服务器对每个 PEKS 密文 C_i 执行测试:
// Test(C_i, trapdoor) → 匹配 or 不匹配

// 如果匹配,返回对应的密文文档

嗯,这里要特别留意 Test 算法。它的本质是一个双线性对运算:如果 C_i 是用关键词 w 加密的,且 trapdoor 也是针对 w 生成的,那么两个配对值会相等。服务器看不懂内容,但它能判断“是否相等”。

技巧:我在实际部署 PEKS 时遇到过性能问题——Test 算法要做一次双线性对,数据量上去以后会很慢。一个常见的优化是给索引加一个 bloom filter 做前置过滤,快速排除不相关的密文,减少无意义的配对运算。但注意,bloom filter 本身可能泄露关键词频率信息,请根据威胁模型评判是否能用。

3.3 SSE vs PEKS:关键差异对比

我自己最常被问到的问题就是:“我到底该选 SSE 还是 PEKS?” 我通常的建议是这样的:

对比维度 SSE(对称可搜索加密) PEKS(非对称可搜索加密)
密钥管理 加密方和搜索方共享同一密钥;密钥分发困难 公钥加密,私钥搜索;密钥分发简单,支持多写一读
索引构建 通常构建加密倒排索引;空间开销随文档数量线性增长 每个关键词独立生成可搜索密文;索引结构较稀疏
查询效率 O(1) 或 O(log n),非常快;适合大批量数据 O(n) 或更差,因为要逐条 Test;计算开销大
安全性 泄露访问模式(查询重复性);安全模型相对成熟 更强的抵抗性,部分方案能隐藏访问模式;但安全模型复杂
典型场景 单用户加密云盘、私有审计日志检索 多源日志汇聚、去中心化数据共享、医疗记录查询

3.4 整体流程:从加密到检索的链路

为了让你有个全局视野,我用一张图把 SSE 和 PEKS 的流程做了对比。左边是 SSE 的对称模式,右边是 PEKS 的非对称模式。

可搜索加密整体流程对比:SSE vs PEKS 可搜索加密流程对比:SSE(对称) vs PEKS(非对称) SSE 对称模式 PEKS 非对称模式 客户端(加密+搜索) 2. 构建加密倒排索引 3. 生成搜索令牌 4. 服务器匹配索引返回密文 5. 客户端解密得明文文档ID 数据发送方(公钥加密) 2. 为每个关键词生成PEKS密文 3. 接收方(私钥)生成陷门 4. 服务器执行Test算法逐条匹配 5. 返回匹配密文,接收方解密 共享密钥 公/私钥

从这张图上能清楚看到两种模式的差异。SSE 把加密和搜索都集中在客户端,服务器只负责存储和匹配;PEKS 则把加密权开放给任何人,但搜索权严格控制。我在实际项目中,如果数据源是可控的(比如我自己写日志),我优先选 SSE,性能优势太明显——毫秒级响应和秒级响应的差距,在线上环境是不可接受的。但如果数据源不可信或需要多方写入,PEKS 才是正确选择,哪怕牺牲一点性能。

一句话总结:SSE 是“一把钥匙开一把锁”,快但受限;PEKS 是“公钥上锁,私钥开锁”,灵活但慢。选哪个,取决于你的数据源分布和性能底线。

3.5 一点实践心得

最后再说点我自己的经验。不管是 SSE 还是 PEKS,索引更新都是一个容易被忽视的坑。我遇到过有人在生产环境里,每次新加文档就重建整个索引——数据量一上去直接 OOM。正确的做法是支持增量更新:SSE 可以把新关键词追加到已有的倒排链上;PEKS 可以给新生成的密文打上时间戳,查询时只扫描增量部分。

另一个常见的错误是密钥轮换。SSE 的主密钥如果轮换,整个索引都需要重新加密,代价巨大。我的建议是:用分层密钥结构——一个主密钥派生多个搜索密钥,轮换时只换顶层主密钥,下层搜索密钥可以不换,或者分批更换。PEKS 的公钥轮换稍微简单一些,新数据用新公钥加密,旧数据仍然能用旧私钥查询,但要注意在安全策略里妥善处置旧私钥的销毁。

好了,关于 SSE 和 PEKS 的基础就讲到这里。这些概念是后面实现 KMS 集成和巡检脚本的基石,理解不透彻的话,后面很容易翻车。