3. 数据加密策略:传输层加密、静态加密与密钥管理

加密这事儿,说白了就是给数据穿上防弹衣。传输中要穿,静止时也要穿。我见过太多团队只做了传输加密,结果数据库一泄露,所有数据裸奔——那感觉,就像家门锁得死死的,窗户却大敞着。

这一章我们聚焦三个核心:TLS/mTLS 保证数据在路上的安全,AES-256 保证数据在磁盘上的安全,以及密钥管理——嗯,这是最容易被忽视但最关键的一环。

3.1 传输层加密:TLS 与 mTLS

先聊 TLS。你访问 HTTPS 网站时用的就是它。客户端验证服务器证书,建立加密通道。对于向量数据库的 API 调用,这是底线要求。

核心要点:所有向量数据库的客户端连接必须启用 TLS 1.2 或更高版本。禁用 SSLv3、TLS 1.0 和 1.1。这些老协议有已知漏洞,比如 POODLE 攻击。

但光有 TLS 够吗?不够。你想想看,TLS 只是客户端验证了服务器身份,服务器并不知道客户端是谁。在向量数据库的场景里,谁在往里面写数据、谁在查数据,这信息同样重要。

这时候就需要 mTLS(双向 TLS)了。客户端和服务端互相验证证书。我在一个金融项目中遇到过这样的需求:不同部门只能访问自己的向量索引,mTLS 配合证书里的 CN 字段做鉴权,效果很好。

我的建议:如果向量数据库部署在内部网络,且客户端数量可控,优先用 mTLS。证书管理虽然麻烦一点,但安全收益明显。我曾经帮一个客户从单向 TLS 迁移到 mTLS,配置过程花了半天,但之后再也没有出现过未授权访问告警。

配置示例:Python 客户端启用 mTLS

import grpc

# 加载证书
with open('ca.crt', 'rb') as f:
    ca_cert = f.read()
with open('client.crt', 'rb') as f:
    client_cert = f.read()
with open('client.key', 'rb') as f:
    client_key = f.read()

# 创建凭证
credentials = grpc.ssl_channel_credentials(
    root_certificates=ca_cert,
    private_key=client_key,
    certificate_chain=client_cert
)

# 建立安全通道
channel = grpc.secure_channel('vector-db.example.com:443', credentials)
# 后续 gRPC 调用自动加密

注意:证书私钥必须妥善保管。我曾经见过有人把私钥直接硬编码在代码里,然后提交到 Git 仓库——这比不加密还危险。使用环境变量或密钥管理服务加载私钥。

3.2 静态数据加密:AES-256

数据到了磁盘上,谁来保护它?攻击者可能拿到硬盘、备份文件、或者直接 dump 数据库文件。这时候 AES-256 就是最后一道防线。

AES-256 是目前公认的对称加密标准。256 位密钥,暴力破解需要的时间比宇宙年龄还长。但算法本身只是工具,怎么用才是关键。

加密模式的选择

模式 特点 适用场景
GCM 认证加密,防篡改 推荐:向量数据加密
CBC 需要填充,无认证 兼容旧系统
CTR 流模式,可并行 高性能场景

我个人习惯用 AES-256-GCM。为什么?因为它同时提供了加密和完整性校验。向量数据一旦被篡改,查询结果就会出错,甚至可能被用来做对抗性攻击。GCM 模式能检测到数据是否被修改过。

避坑指南:我曾经接手过一个项目,他们用 AES-256-CBC 加密向量数据,但没有做 HMAC 校验。结果有人篡改了存储文件,系统毫无察觉,导致推荐结果完全错乱。从那以后,我坚持用 GCM 模式。

加密粒度:全量 vs 字段级

这里有个选择:是整个数据库文件加密,还是只加密向量字段?

  • 全量加密(TDE):数据库层面透明加密,对应用无感。但密钥管理在数据库层,粒度粗。
  • 字段级加密:应用层加密向量数据后再写入。粒度细,但需要管理每个字段的密钥。

我的建议是:如果向量数据是核心资产,用字段级加密。虽然实现复杂一点,但你可以控制谁有权限解密。我在一个医疗 AI 项目中就是这么做的——患者的诊断向量只有授权医生才能解密查看。

代码示例:字段级 AES-256-GCM 加密

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os

# 生成密钥(实际生产中从 KMS 获取)
key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)

def encrypt_vector(vector_bytes: bytes) -> bytes:
    nonce = os.urandom(12)  # 96 位随机 nonce
    ciphertext = aesgcm.encrypt(nonce, vector_bytes, None)
    return nonce + ciphertext  # 拼接存储

def decrypt_vector(encrypted_bytes: bytes) -> bytes:
    nonce = encrypted_bytes[:12]
    ciphertext = encrypted_bytes[12:]
    return aesgcm.decrypt(nonce, ciphertext, None)

重要:nonce 必须唯一。用同一个 nonce 加密两个不同的向量,密钥就会被破解。我建议用计数器或 UUID 作为 nonce,确保不重复。

3.3 密钥管理方案

加密算法再强,密钥泄露就全完了。密钥管理是整个加密体系的命门。说实话,很多公司在这上面栽过跟头。

我总结了一套密钥管理的分层模型,画了个图方便理解。

密钥管理分层架构 用户主密钥(CMK) 由 KMS 硬件安全模块保护 密钥加密密钥(KEK) 用于加密数据密钥,定期轮换 数据密钥(DEK) 实际加密向量数据的密钥,每个索引可独立 加密后的向量数据 存储在磁盘或对象存储中 每一层密钥只用于保护下一层,CMK 永远不离开 KMS 硬件

这个分层模型的核心思想是:密钥永远不直接暴露。应用层只使用数据密钥(DEK),而 DEK 本身被密钥加密密钥(KEK)加密存储。KEK 又由用户主密钥(CMK)保护,CMK 放在 KMS 硬件里。

密钥轮换策略

密钥不能一成不变。我建议的轮换周期:

  • CMK:每年轮换一次,或发生安全事件时立即轮换
  • KEK:每季度轮换一次
  • DEK:每月轮换一次,或每个新索引创建时生成新的

我的经验:轮换 DEK 时不需要重新加密所有数据。只需要用新 DEK 加密新写入的数据,旧数据保留旧 DEK 加密。查询时根据数据的时间戳选择对应的 DEK 解密。这叫「惰性轮换」,能大幅降低性能开销。

密钥存储方案对比

方案 安全等级 运维复杂度 成本
AWS KMS / Azure Key Vault 高 低 中
HashiCorp Vault 高 中 低(开源)
本地 HSM 极高 高 高
配置文件硬编码 无 极低 极低

警告:最后一种方案「配置文件硬编码」我见过不止一次。每次看到都头皮发麻。如果你预算有限,至少用 HashiCorp Vault 社区版。免费,但比硬编码安全一万倍。

3.4 综合实践建议

好了,把三个部分串起来。一个完整的向量数据库加密方案应该长这样:

  1. 传输层:启用 mTLS,客户端和服务端双向证书验证
  2. 静态层:使用 AES-256-GCM 对向量字段做字段级加密
  3. 密钥层:采用三层密钥体系,CMK 由 KMS 管理,KEK 和 DEK 定期轮换
  4. 审计:记录所有密钥访问日志,定期审查

我记得有一次做安全审计,对方问我:「你们加密做得这么复杂,性能还能接受吗?」我当时的回答是:安全是有代价的,但数据泄露的代价更大。实际上,AES-256-GCM 在硬件加速下,对向量查询的性能影响不到 5%。这个代价,值得付。