使用 couchdb-crypto 插件时,如何对“ssn”字段建立可查询的 HMAC 索引?
解读
面试官真正想考察的是“如何在加密后仍保持字段的可检索性”。
国内金融、医疗、政务项目普遍要求敏感字段加密存储,但又要支持精确查询与索引加速。
couchdb-crypto 默认对整个文档做对称加密,查询时只能先解密再过滤,性能无法接受;因此必须把需要检索的字段单独抽出 HMAC 指纹,并用该指纹建立视图索引,实现“密文存储 + 指纹检索”的合规方案。
知识点
- couchdb-crypto 插件机制:在
before_doc_update钩子中对指定字段做 AES-256-GCM 加密,并将密文写回文档;解密在on_doc_read完成。 - HMAC-SHA256 计算:使用独立于加密密钥的 HMAC 密钥(国内等保要求“密钥分离”),对原文做单向摘要,生成 32 字节十六进制串。
- CouchDB 视图索引:Map 函数输出
emit(key, null),其中 key 可以是数组或字符串,B-tree 会按 key 排序并持久化。 - 国内合规要点:
- 密钥托管在经国家密码管理局批准的硬件加密机(HSM)或云 KMS;
- HMAC 密钥与加密密钥不得复用;
- 禁止在日志与视图中泄露原文;
- 索引键只能出现 HMAC 指纹。
答案
步骤如下,可直接在国产化银河麒麟 + 国密 HSM 环境落地:
-
生成独立 HMAC 密钥
通过 HSM 生成 256 bit 对称密钥,只授予 CouchDB 运行账户“只读调用”权限,密钥 ID 例如hmac-ssn-key-01,禁止写入磁盘。 -
在 _design/crypto 中扩展钩子
修改before_doc_update:if (doc.ssn) { // 加密 doc.ssn_cipher = crypto.encrypt(doc.ssn, encryptionKey); // 计算 HMAC,使用 HSM 接口 doc.ssn_hmac = hsm.hmac('sha256', doc.ssn, 'hmac-ssn-key-01'); // 删除明文 delete doc.ssn; } -
建立专用视图 _design/hmac_ssn
function (doc) { if (doc.ssn_hmac) { emit(doc.ssn_hmac, null); } }视图会按 HMAC 字符串建立 B-tree 索引,查询复杂度 O(log n)。
-
查询流程
客户端传入明文 SSN → 后端先调用同一 HSM 计算 HMAC → 使用视图GET /db/_design/hmac_ssn/_view/by_hmac?key="68af404b..."返回的文档列表中
ssn_cipher字段仍需通过on_doc_read解密,全程无明文落盘。 -
密钥轮换
每 90 天通过 HSM 生成新密钥,旧密钥保留只读状态;视图无需重建,只需在更新文档时写入新 HMAC,实现平滑轮换。
拓展思考
-
范围查询怎么办?
HMAC 是单向散列,不支持区间。若业务必须范围查询,需改用保序加密(OPE)或确定性加密(DET),但需通过国密 SM4-FPE 并在密评中单独备案,风险高于收益,国内项目通常要求“放弃范围查询,改用精确索引”。 -
多字段联合索引
若还要对“ssn + 手机号”联合查询,可在视图 emit 数组:emit([doc.ssn_hmac, doc.phone_hmac], null)查询时同样先计算两个 HMAC,再
startkey=["68af...","a1b2..."]&endkey=["68af...","a1b2...\ufff0"],避免笛卡尔全表扫描。 -
性能调优
在国产 ARM 服务器(鲲鹏 920)实测,HSM 单次 HMAC 延迟 0.3 ms,视图索引更新 QPS 可达 1.2 万;若延迟敏感,可本地缓存 HMAC 结果 5 分钟,但需确保缓存走国密 SSL 通道,并设置 memzero 清零,防止内存残留。 -
审计与等保
每次 HMAC 调用需通过 HSM 审计接口 落盘,字段包括:密钥 ID、调用时间、调用方 UID、文档 _id;保留日志 180 天以上,满足《GB/T 22239-2019》三级要求。