如何基于 _users 角色动态开启新字段加密?
解读
国内金融、政企类项目把“字段级加密”视为等保 2.0 与《个人信息保护法》落地的硬指标。CouchDB 自身只提供传输层 TLS与磁盘级 AES256(通过 dm-crypt/LUKS),没有字段级加密 API。
面试官真正想看的是:
- 你能否在不破坏 CouchDB 多主复制、离线同步的前提下,把“谁(角色)能看到什么(字段)”做成可热更新的策略;
- 你能否用** CouchDB 原生机制**(设计文档、validate_doc_update、_users 角色、Erlang 钩子)把加密逻辑“无侵入”地插进去;
- 当角色变更时,旧数据能否自动重加密、新数据能否零停机切换算法。
知识点
- _users 数据库:每个用户文档含 name、roles、password_sha、salt,roles 数组是 CouchDB 唯一的原生 RBAC 入口。
- 设计文档(_design/ddoc):validate_doc_update 函数在写前触发,可读取 userCtx.roles,是“角色驱动”的唯一官方钩子。
- Erlang 预处理钩子(couch_db_updates、couch_doc_validate):官方未文档化,但国内私有云普遍二次开发,可拦截 doc 序列化字节流,实现字段级加解密而不污染 JSON。
- 加密策略文档(_crypto_policy):自建普通文档,用 roles 字段做白名单,通过 validate_doc_update 保证只有 admin 可改,实现“动态开关”。
- 密钥轮换:用 KMS(国密 SM4/SM9 或阿里云 KMS) 做数据密钥(DEK)加密,KEK 不落盘,满足《金融数据安全 数据安全分级指南》要求。
- 离线同步兼容性:加密后字段必须是合法 JSON 字符串(base64 或 JWE),否则 PouchDB 同步会解析失败;不能改 _id、_rev、_revisions 字段,否则复制会断。
答案
-
建策略库
在 _crypto_policy 数据库里放“策略文档”:{ "_id": "policy/customer", "fields": ["idCard", "bankCard"], "roles": ["role_finance"], "cipher": "SM4/GCM/128", "dekId": "dek-202406", "version": 3 }只有 admin 能改,validate_doc_update 里校验 userCtx.roles 包含 _admin。
-
写 Erlang 预处理钩子(国内私有云常用方案)
在本地.ini 打开[couch_db] pre_doc_update = my_app_crypto:encrypt_doc/1 post_doc_read = my_app_crypto:decrypt_doc/1钩子逻辑:
- 读 userCtx.roles,与策略库做交集,算出本次要加密的字段列表;
- 对命中的叶子值使用策略里的 cipher 与 DEK,生成 {"@cipher":"SM4","@dek":"dek-202406","@iv":"…","@ct":"…"} 对象,原字段名保留,保证复制时结构不变;
- 解密时若用户角色已不在白名单,直接返回 null,防止客户端残留密钥。
-
角色变更触发重加密
监听 _users 的 change feed,当某用户 roles 被剥夺,立即触发后台作业:- 用 mango 索引找出所有该用户曾经写入、且含敏感字段的 doc;
- 重新加密,version+1,保留旧 DEK 以便审计;
- 写回时使用 bulk_docs?new_edits=false 保证 _rev 连续,不会导致复制冲突。
-
热更新算法
修改策略文档,version 自增,钩子发现 version 差异后,对新写入字段用新 cipher,旧字段仍用老 key 解密,实现零停机灰度。 -
国密合规
全程使用硬件加密机(HSM) 生成 KEK,DEK 用 KEK 做密钥包裹后存策略文档,满足《GM/T 0054-2018》要求;日志走 syslog-ng 到省厅堡垒机,审计留存 180 天。
拓展思考
- 如果客户坚持用开源版 CouchDB(不能改 Erlang),你可以把加密逻辑挪到中间层(Go 微服务),通过双写(一份明文进 CouchDB,一份密文进 CouchDB)实现,但要自研冲突解决器;此时_users 角色只能做读拦截,写加密在微服务完成,牺牲离线直写能力。
- 对移动端 PouchDB,可把策略文档随 replication 一起下到本地,用 WebAssembly 执行国密算法,实现离线加密写入;上线后再同步回 CouchDB,此时要防止策略回滚攻击,需在策略文档加时间戳 + 签名。
- 当数据需要跨域机房容灾时,加密策略必须随复制一起同步,否则灾备库无法解密;可在 _replicator 文档里加自定义头 x-crypto-policy-version,目标库钩子读到后自动拉取对应策略,实现策略级联。