密码学基础回顾:哈希、加密、HMAC 与 KDF

做盲索引之前,我先把密码学基础拉一遍。你别觉得这些是小儿科——在实际项目中,索引泄漏的坑,九成以上都出在基础认知不牢上。我个人习惯是,每次设计新协议前,先拿这四个工具在脑子里过一遍:能用谁、不能用谁、组合起来有什么副作用。

1. 哈希函数:单向道的核心

哈希函数是个黑盒子。你扔进去任意长的数据,它吐出一个固定长度的摘要。关键特性就两条:抗原像性和抗碰撞性。

  • 抗原像性:从摘要反推原文,计算上不可行。
  • 抗碰撞性:找不到两个不同的输入,让它们哈希值相同。

常用的有 SHA-256、SHA-3。MD5 和 SHA-1 已经被撞破了,千万别再用。

个人经验:我在项目中遇到过用 MD5 做数据去重的场景。当时两个完全不同的大文件,MD5 值居然一样——虽然概率极低,但碰上就是灾难。从那以后,但凡涉及安全校验,我至少用 SHA-256。

哈希函数的输出是不可逆的。这在盲索引里意义重大:我们把原始标识符,比如用户ID或文档哈希,作为查询的基底。服务器只知道哈希值,不知道原文。但要注意,单靠哈希不够——如果枚举空间小,比如手机号或身份证号,攻击者可以自己算一个彩虹表,直接查表还原。

2. 对称加密:保密性第一

对称加密大家都不陌生。加密和解密用同一个密钥。常见的算法有 AES、ChaCha20。

AES 是目前的标准,密钥长度支持 128、192、256 比特。模式上,ECB 不要用——同样的明文块加密结果相同,模式泄漏非常致命。我个人建议用 GCM 或 CTR 模式,自带认证或作为流密码使用。

模式 安全性 推荐场景 我踩过的坑
ECB 低,模式泄漏 不推荐 图像加密后轮廓可见,明文边界一目了然
CBC 中等 文件加密 填充预言攻击需额外注意
GCM 高,自带认证 网络传输、盲索引查询 nonce 必须唯一,重复即泄露
CTR 高,流密码 高性能场景 同样nonce不能复用

在盲索引里,对称加密常用于加密索引的叶子节点。你想想看,如果索引项是密文的,服务器即使拿到数据,也看不出内容。但密钥管理就成了新的挑战——这个我们后面会讲。

3. HMAC:防篡改的标签

HMAC 是哈希函数的升级版。它引入了一个 密钥,把密钥和消息揉在一起再哈希。这样输出的标签,只有持有密钥的人才能验证。

为什么要用密钥?因为哈希函数本身是公开的,谁都能算。加上密钥后,同时保证了完整性和真实性。消息在传输中被篡改,HMAC 校验一定会失败。

// 伪代码示例
key = "my-secret-key"
message = "user-123"
hmac_result = HMAC-SHA256(key, message)
// 输出:a3f2b8c1d9e0...

在盲索引的构造中,HMAC 特别适合用来做伪随机化。举个例子:用户的邮箱作为查询条件。如果直接用哈希,攻击者可以枚举常见邮箱。用 HMAC 加一个只有服务端知道的密钥,枚举就没用了——因为你不知道密钥,算不出正确的 HMAC 值。

注意:HMAC 的密钥必须随机生成且妥善保管。我曾经见过直接用"123456"做 HMAC 密钥的代码,那还不如不加密。你在实际项目中,至少用 32 字节的随机字节。

4. 密钥派生函数:从密码生成密钥

KDF 解决的问题很简单:用户输入一个密码,系统需要得到一个密钥。但密码往往熵很低,直接哈希容易被暴力破解。KDF 通过 慢哈希 和 加盐,大幅提高暴力破解成本。

常见的 KDF:

  • PBKDF2:基于密码和盐,反复迭代哈希。迭代次数越高越安全。
  • scrypt:除了迭代,还占用大量内存,对抗硬件攻击效果好。
  • Argon2:目前最推荐,2015 年密码竞赛获胜者,能同时控制 CPU 和内存开销。
// PBKDF2 示例(Python)
import hashlib
import os

salt = os.urandom(16)
dk = hashlib.pbkdf2_hmac('sha256', b'my_password', salt, 100000)
print(dk.hex())

在盲索引体系里,KDF 最常见的用法是派生索引加密密钥。客户端从用户口令派生出一个子密钥,用来加密或计算索引值。这样的话,即使服务端的索引库被拖走,没有用户口令,攻击者也无法解密任何数据。

这里有个隐藏的坑:盐必须存储在客户端。如果盐存服务端,每次查询都要先取盐,那就多了一次网络交互,而且盐本身也成了攻击面。我习惯把盐直接写在用户设备的本地存储里,查询时直接拿到。

5. 核心知识体系一览

下面这张图,把四种工具的关系和分工画清楚了。你看一眼,心里就有数了。

密码学基础工具 · 核心知识体系 哈希函数 对称加密 HMAC KDF 单向性 · 固定输出 抗碰撞性 ⚠ 彩虹表风险 盲索引 标识符隐藏 AES · ChaCha20 ECB ❌ CBC ⚠ GCM ✅ 推荐 nonce 唯一性 索引项加密 密钥 + 哈希 完整性 · 真实性 弱密钥风险 伪随机索引 服务端视角 慢哈希 · 加盐 PBKDF2/scrypt Argon2(推荐) 派生加密密钥 客户端本地盐 盲索引核心思路:哈希隐藏标识、加密保护索引、HMAC防伪、KDF派生密钥

你看,四种工具各有各的位子。哈希做基础隐藏,加密做内容保护,HMAC 做认证,KDF 解决密钥派生。盲索引的核心思路,就是把它们组合起来,做到查询过程不泄漏任何原始信息。

6. 实战避坑清单

说了这么多,我总结几个实战里最容易翻车的点:

  1. 别用自定义哈希。不管你觉得自己多聪明,用 SHA-256 总是没错的。
  2. 密钥管理是最薄弱的环节。我曾经见过把 AES 密钥直接硬编码在代码里的项目——那还不如不加密。
  3. HMAC 不能代替数字签名。HMAC 是对称的,掌握密钥的人可以伪造消息。在多方场景下,你需要非对称手段。
  4. KDF 的迭代次数不能太少。PBKDF2 至少 10 万次迭代,Argon2 的时间成本参数要设到 3 以上。
  5. nonce 或 IV 必须唯一。在 GCM 模式下,nonce 重用直接导致密钥流泄漏,加密等于废纸。
记住这个公式:盲索引的安全性 = 哈希的抗碰撞性 × 加密的机密性 × HMAC 的认证性 × KDF 的密钥强度。任何一个环节出问题,整个系统就塌了。