密码学基础回顾:对称加密、非对称加密、哈希函数、伪随机函数

做可搜索加密,你绕不开这几个老朋友。对称加密、非对称加密、哈希函数、伪随机函数——它们就像工具箱里的螺丝刀、扳手、钳子和卷尺。少一个,活就没法干。

我个人习惯先把它们拉出来理一遍。不是为了复习课本,而是为了打通之后盲索引设计的任督二脉。你想想看,没有伪随机函数,盲化怎么做?没有哈希,索引怎么定址?底子不牢,后面代码写到一半就得懵。

对称加密:又快又稳的看门狗

对称加密,说白了加密和解密用同一把钥匙。就像你家的门锁,锁门和开门的钥匙是一模一样的。我最常用的是 AES-256-GCM,既有认证加密,又能扛住很多侧信道攻击。

一个典型场景: 在可搜索加密里,我们经常用对称加密去加密文档本身。钥匙由用户保管,服务器只存密文。搜索时用盲化的令牌去匹配索引,但不碰文档本体。

举个例子,用 Python 的 cryptography 库加密一段数据:

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import os

key = os.urandom(32)  # AES-256 密钥
nonce = os.urandom(12)  # GCM 模式 nonce 推荐 12 字节
cipher = Cipher(algorithms.AES(key), modes.GCM(nonce), backend=default_backend())
encryptor = cipher.encryptor()
ciphertext = encryptor.update(b"Hello, searchable world!") + encryptor.finalize()

嗯,这里要注意:nonce 必须唯一,否则 GCM 会直接跪。我曾经在一个项目里没处理好 nonce 轮换,结果同一密钥下出现了两次相同的 nonce,导致认证标签被攻破。幸好是内部测试,不然就搞出大新闻了。

非对称加密:公钥私钥的二人转

非对称加密就不同了。加密用公钥,解密用私钥。公钥可以到处贴,私钥锁在保险柜里。常见的是 RSA 和 ECC(椭圆曲线)。在可搜索加密中,非对称加密常用于密钥交换或构建更复杂的密文策略。

特性对称加密非对称加密
密钥数量1 个(共享密钥)2 个(公钥+私钥)
计算速度极快(硬件加速)慢(百倍量级)
典型算法AES, ChaCha20RSA, ECDH, Ed25519
密钥长度128~256 bit2048~4096 bit(RSA)
适用场景批量数据加密密钥协商、数字签名

为什么非对称慢?因为数学运算复杂。RSA 靠大整数分解,ECC 靠椭圆曲线上的点乘。你在可搜索加密里要是用非对称去加密文档,性能会很难看。所以我建议你只拿它来做密钥协商或生成短小的密文令牌。

哈希函数:捏泥巴一样的数据指纹

哈希函数接受任意长度的输入,输出固定长度的摘要(比如 SHA-256 输出 256 位)。它的特点是:不可逆、抗碰撞、雪崩效应。改一个比特,输出就面目全非。

在可搜索加密里,哈希函数经常用来构造索引的键。举个例子:把文档中的关键词哈希一下,然后把哈希值当作索引的地址。但直接哈希会面临“确定性泄露”——同一个关键词永远对应同一个哈希。所以我们需要加盐(salted hash)或者用伪随机函数来盲化。

避坑提醒: 千万别用 MD5 或 SHA-1!我见过有团队拿 MD5 做索引键,被彩虹表秒破。现在起码要用 SHA-256,而且最好配合密钥一起做 HMAC 或 PRF。

来看一个简单的 SHA-256 使用示例:

import hashlib

msg = b"keyword_to_index"
digest = hashlib.sha256(msg).hexdigest()
print(digest)  # 64 位十六进制字符串

但你想想看,如果服务器知道这个哈希值,就能对字典里的每个候选词做一遍哈希,然后匹配。这就是确定性哈希的致命伤。所以真正的盲索引要用伪随机函数来代替。

伪随机函数:可搜索加密的魔法引擎

伪随机函数(PRF)是一个带密钥的确定性函数。输入一个值和密钥,输出一个看起来完全随机的值。同样输入再跑一次,结果一样。但是因为没有密钥的人无法猜测输出,所以它完美替代了哈希函数在索引中的角色。

说它“伪随机”,是因为理论上它并不是真随机——只要知道密钥就能复现。但这就是我们想要的:用户掌握密钥,服务器没有密钥,那么服务器看到的输出就是随机的。

标准的 PRF 构造可以用 HMAC-SHA256 或者 AES-CMAC。我一般用 HMAC-SHA256,因为 Python 标准库直接支持,不用额外依赖。

import hmac
import hashlib

key = b"32_byte_secret_key_here..."  
msg = b"search_term"
prf_output = hmac.new(key, msg, hashlib.sha256).digest()
print(prf_output.hex())  # 64 字节的伪随机输出
核心要点: 在盲索引中,用户用 PRF 把关键词盲化成索引的键,因为输出不可预测,服务器无法反推原文。PRF 的可重复性保证了相同关键词得到相同盲化结果,从而支持精确搜索。

搞清楚了这四个基础部件,接下来我们就能搭积木一样把盲索引系统建起来。下面这张图把它们的定位和关系串了起来。

密码学基础四件套 —— 可搜索加密视角 可搜索加密基础 对称加密 非对称加密 哈希函数 伪随机函数 PRF AES / ChaCha20 RSA / ECC SHA-256 / BLAKE2 HMAC-SHA256 PRF 是盲索引的核心引擎 替代哈希函数,抗字典攻击

你看,对称加密负责数据的机密性,非对称加密处理密钥交换,哈希函数提供不可逆的指纹,而伪随机函数——嗯,它就是那个让盲索引名副其实的魔法。后面几节我们就要用 PRF 来构建真正的盲索引方案了。先把这些概念在脑子里过一遍,实操起来会顺畅得多。

我的一个经验: 在实际代码中,我通常把 PRF 密钥和加密密钥分开管理。一个用于盲化关键词,另一个用于加密文档。这样即使索引被攻破,文档还有另一层保护。