密码学基础回顾:端到端密文检索的硬核根基
好,咱们正式开始进入密码学的世界。说实话,很多人觉得密码学就是一堆数学定理和密钥,枯燥得很。但在我做密文检索系统的这些年里,我越来越觉得——密码学不只是工具,它是整个安全架构的信任锚点。没有它,后面所有的“端到端”都是空中楼阁。
这一章我们只聊四样东西:对称加密、公钥加密、哈希函数、随机数。它们每一个都在后续的密文检索里扮演着不可替代的角色。你想想看,一个检索系统要能在看不见明文的情况下给你准确结果,靠的就是这些算法的巧妙组合。
对称加密:AES 才是那个干活的人
聊到对称加密,很多人第一反应是“加密解密用同一个密钥”。对,但不全对。在密文检索里,AES 承担了最沉重的数据保护任务。我习惯把对称加密比作“安全的铁柜”——你往里面放数据,上锁给服务器,服务器拿不到钥匙,只能在这个柜子上做有限的检索操作。
核心要点:在密文检索中,对称加密主要用于保护实际数据内容。我们用 AES-256-GCM 模式比较多,因为同时提供了加密和完整性校验。记住,加密模式的选择直接决定了安全性。
举个例子,用户的敏感文档或医疗记录,在本地用对称密钥加密后上传。服务器永远看不到明文,但它能在密文上执行特定的检索算法。为什么能做到?因为索引被设计成了某种加密后仍保留比较关系的形式——这块后面会详细讲。
// 伪代码示例:AES-256-GCM 加密流程
// key 是客户端生成的对称密钥
encryptedData = AES_GCM_Encrypt(plaintext, key, nonce)
// 输出: 密文 + 认证标签 + nonce
// 注意: nonce 每次加密必须唯一!
我的经验:有一次项目里,团队成员直接把 nonce 设成了固定值。上线前被我拦住了。AES-GCM 的 nonce 一旦重用,攻击者可以通过密文差异推导出部分明文信息。这种坑,我在早期项目中踩过,大家千万别重蹈覆辙。
公钥加密:RSA 和 ECC 的分工不同
对称加密好是好,但密钥怎么安全地传给更多人?这就是公钥加密登场的时候了。RSA 和 ECC 在密文检索中主要负责密钥交换和身份认证。我个人更喜欢 ECC,因为在相同安全强度下,它的密钥更短,计算更快。
| 特性 | RSA | ECC(椭圆曲线) |
|---|---|---|
| 安全性基础 | 大整数因子分解难题 | 椭圆曲线离散对数难题 |
| 密钥长度(128-bit 安全级) | 3072 位 | 256 位 |
| 计算速度 | 慢 | 快 |
| 在密文检索中的用途 | 数字签名、密钥封装 | 密钥协商、身份验证 |
你可能会问:为什么不在密文检索里直接用公钥加密全部数据?性能不允许。公钥加密的速度比对称加密慢几个数量级。我见过的真实系统,都是“公钥加密用来交换对称密钥,对称密钥用来加密实际数据”这种混合模式。
注意:千万不要用 RSA 直接加密大量数据。我曾经在代码评审时见过有人拿 RSA 逐字段加密 JSON 对象,结果响应时间直接飙到几十秒。正确做法是用 RSA 封装 AES 密钥,用 AES 加密正文。
哈希函数:不只是密码学上的“指纹”
讲完加密,我们来看看哈希。哈希函数在密文检索里太关键了——它负责把数据变成固定长度的索引指纹。你想想看,要在一个密文库里搜索一个关键词,服务器总不能把所有密文都解密一遍吧?所以我们需要对关键词本身做哈希,生成一个“陷门”,然后服务器拿着陷门去匹配索引。
// 哈希在密文检索中的典型用法
// 对关键词生成索引
index = SHA256(keyword + salt)
// 搜索时,用户发送陷门
trapdoor = SHA256(searchKeyword + same_salt)
// 服务器匹配:trapdoor 是否等于某个 index
避坑指南:我早期做项目时,直接用 SHA256 对关键词做哈希当作索引。结果发现——相同关键词的索引永远一样,服务器能统计出词频!后来加了盐值(salt)才解决。每个文档的盐值不同,这样即使两个文档包含同一个词,它们的哈希索引也不同,服务器没法做频率分析。
另外,密码学安全的哈希函数(SHA-2/3)和普通哈希完全不同。MD5、SHA-1 已经被攻破了,不要再用了。我见过一些系统用 SHA-1 做索引,后来发现可以构造碰撞——这相当于让两个不同的关键词有了相同的索引,检索结果就乱套了。
核心原则:密文检索中使用的哈希函数必须是抗碰撞的密码学哈希。SHA-256 是目前最低要求。如果追求更高的安全性,可以考虑 SHA-3。
随机数与伪随机函数:密钥生成的第一道防线
最后,我们来谈谈随机数。很多人觉得随机数嘛,不就是 Math.random() 吗?绝不可以。尤其是在密码学里,随机数质量直接决定了密钥的不可预测性。
在密文检索系统里,随机数用在哪?几乎所有地方。生成对称密钥、生成 RSA/ECC 密钥对、生成盐值、生成 nonce……如果随机数质量不够好,攻击者可能通过统计分析预测出密钥的部分信息。我曾经在渗透测试中遇到过一个系统,用的是 rand() 生成的“密钥”,结果发现它只是当前毫秒级时间戳的变形——几乎等于明文。
正确的做法是用操作系统提供的 CSPRNG(密码学安全的伪随机数生成器),比如 Linux 上的 /dev/urandom,或者 OpenSSL 的 RAND_bytes()。
// 正确的随机数生成方式
// 在 C/C++ 中使用 OpenSSL
unsigned char key[32];
RAND_bytes(key, sizeof(key));
// 在 Python 中使用 secrets 模块
import secrets
key = secrets._bytes(32)
警告:永远不要使用标准库的伪随机数生成器(如 random()、rand())生成密码学密钥。我曾经见过一个开源项目,内部测试时用了 rand() 生成密钥,结果上线后没改——漏洞就是从这里爆发的。记住,密码学安全和非密码学安全的随机数生成器,只有一字之差,安全性却是天壤之别。
四个组件在密文检索中的协作关系
好了,我们把这四个东西串起来,看看它们怎么一起工作。
- 随机数 生成对称密钥和盐值
- 对称加密(AES) 加密文档内容和部分索引
- 哈希函数 对关键词加盐后生成搜索索引
- 公钥加密(RSA/ECC) 安全分发对称密钥给合法用户
你想想看,一个合法的检索请求来了——用户用私钥解密出对称密钥,用对称密钥本地解密检索结果。服务器在这个过程中,始终接触不到明文。这就是端到端安全的精髓所在。