一、为什么 NoSQL 之后脱敏要重新设计
过去十年,敏感数据防护的讨论几乎都围绕 MySQL、Oracle、SQL Server 这类关系库展开,字段级加密、动态脱敏、运维管控的产品成熟度也最高。但当数据形态从"二维表"走向"图"和"文档"之后,脱敏的难点发生了本质变化。
在关系库里,敏感字段是清晰的列,脱敏规则可以直接挂到列名上:对id_card做掩码,对phone做保留格式加密,对address做分段遮蔽。这种"列—规则"的映射在图数据库和文档数据库里会立刻失效,原因有三个:
- 结构不确定:MongoDB 的文档允许嵌套、允许同一集合里不同文档拥有不同字段,脱敏引擎无法像对表列那样事先枚举所有敏感位置。
- 关联即数据:在 Neo4j 里,节点之间的关系(边)本身携带属性,关系属性里可能就有敏感信息(例如
转账金额、亲密度、共同好友数),这些属性既不在"表"里,也不在"文档"里。 - 图遍历会扩散敏感面:一次 Cypher 查询从起点节点出发,沿多跳关系扩散,沿途经过的节点和边属性都会被带出来。如果只在入口处判断权限,脱敏就会漏掉遍历中途暴露的字段——这正是内部数据泄露高发区。
换句话说,关系库的脱敏是"按列拦截",图与文档的脱敏是"按路径、按结构拦截"。后面几节我们分别拆解这两种形态的工程做法。
1.1 两类 NoSQL 的敏感面对比
| 数据形态 | 敏感位置 | 脱敏难点 | 传统列脱敏能否直接套 |
|---|---|---|---|
| 关系表 | 列 | 结构固定、易于枚举 | 能 |
| 图数据库(Neo4j) | 节点属性、边属性、图路径 | 遍历扩散、关联键需保真 | 不能直接套 |
| 文档数据库(MongoDB) | 顶层字段、嵌套子文档、数组元素 | 结构动态、深度不固定 | 不能直接套 |
可以看到,图与文档的脱敏必须把"结构"和"路径"作为一等公民,而不能只盯着字段名。
二、图数据库的动态脱敏:节点、边与遍历
图数据库的核心价值在于"关系可被高效遍历"。但关系同时也是敏感载体。一个典型的风控图谱里,节点可能是"人"和"企业",边可能是"担保"“持股”“通话”。节点属性里藏着身份证、手机号;边属性里藏着金额、频次。一旦运维人员或下游应用跑一条多跳查询,返回的整张子图都可能是敏感源。
因此图数据库的字段级脱敏要同时解决三个问题:
- 节点属性脱敏:在返回节点时,对
id_card、phone等属性按访问主体做掩码或加密。 - 边属性脱敏:边(关系)上的属性同样要纳入脱敏范围,不能因为是"关系"就忽略。
- 关联键保真:脱敏后,节点的关联键(例如用于 JOIN 的内部 ID、用于聚合的群体标识)必须保持可用,否则图谱的连通性被破坏,图算法全部失效。
2.1 关联键保真:图谱可用性的底线
这是图脱敏最容易被忽视、也最关键的一点。很多人把"脱敏"简单理解成"把敏感值替换成星号",但在图里如果连关联键(node_id、community_id)一并打星,那么:
- 同一实体的多次出现无法被识别为同一个节点;
- 图遍历的"去重"“聚合”"社群发现"全部失真;
- 脱敏后的图对分析毫无价值,等于把数据废掉。
正确的做法是区分"敏感属性"与"关联属性":敏感属性(身份证、手机号)按策略遮蔽或加密,关联属性(实体主键、社群编号、图内索引)保持原值或仅做不可逆但稳定的映射(如哈希取模),确保图谱的拓扑与聚合仍然成立。关联键即使不遮蔽,也往往不具备直接可识别性(一串内部 ID 本身不是个人信息),因此不构成数据库防泄露的主要风险面。
2.2 图遍历脱敏的判定时机
图遍历脱敏有一个核心工程决策:脱敏在什么时机做?两种主流方案:
- 结果态脱敏:先按原语义完整遍历出子图,再对返回结果里的每个节点/边属性逐层脱敏。实现简单,但遍历过程中如果引擎内部做日志、缓存、临时物化,仍可能落敏感明文。
- 遍历态脱敏:在遍历展开每一跳时,对即将进入结果集的节点/边属性实时判定并脱敏。更安全,但对引擎的拦截点要求更高,必须能hook到遍历的"产出阶段"。
工程上推荐"遍历态为主、结果态兜底"的双层判定:在遍历产出节点/边时实时脱敏,最终再对结果集做一次全量扫描兜底,防止边属性或聚合字段被漏判。
2.3 图遍历脱敏伪代码
下面给出一段图遍历脱敏的伪代码,表达"沿路径展开、对节点与边属性按主体策略脱敏、关联键保真"的逻辑(语义示意,非真实产品命令):
函数 graph_mask_traverse(query, subject): # query: 一条多跳 Cypher 类查询 # subject: 访问主体(角色、来源IP、权限级别) plan = parse(query) # 解析出遍历起点、跳数、返回属性集 policy = load_policy(subject, plan.graph) # 加载该主体的脱敏策略 result = 空图 栈 = [起点节点] while 栈非空: 节点 = 栈.pop() if 节点 已访问: continue 标记 节点 已访问 # 节点属性脱敏:区分敏感属性与关联属性 masked_node = 新建节点(节点.id) # 关联键 id 保真 for 属性名, 属性值 in 节点.properties: if 属性名 in policy.敏感属性集: masked_node[属性名] = mask(属性值, policy[属性名]) else: masked_node[属性名] = 属性值 # 非敏感与关联键原样保留 result.加入节点(masked_node) # 沿边展开,边属性也要脱敏 for 边 in 节点.出边: 边属性脱敏后 = {} for k, v in 边.properties: if k in policy.敏感属性集: 边属性脱敏后[k] = mask(v, policy[k]) else: 边属性脱敏后[k] = v result.加入边(节点.id, 边.终点id, 边属性脱敏后) 栈.push(边.终点节点) # 结果态兜底扫描,防止漏判 result = post_scan_mask(result, policy) return result这段伪代码的关键点在三处:第一,节点.id作为关联键在脱敏后仍然保留,保证图谱连通;第二,边属性与节点属性走同一套mask函数,保证脱敏口径一致;第三,返回前post_scan_mask做一次全量兜底,这是防止遍历中途漏判的最后一道闸。
三、文档数据库的递归脱敏:BSON 嵌套结构
MongoDB 使用 BSON 存储文档,文档可以任意嵌套对象、数组、数组里的对象、对象里的数组。一条订单文档可能长这样(示意):
{"order_id":"O20240601001","buyer":{"name":"张三","id_card":"110101199003071234","contact":{"phone":"13812345678","email":"zhangsan@example.com"}},"items":[{"sku":"A100","price":99,"note":"备注含地址:北京市朝阳区xx路1号"},{"sku":"A101","price":200,"note":"无"}],"ship_to":{"phone":"13812345678","address":"北京市朝阳区xx路1号"}}你会发现敏感字段id_card、phone、email、address分散在buyer、contact、ship_to以及items数组的note文本里。如果按"顶层字段"做脱敏,嵌套两层就全部漏掉;如果按"文档ID"整篇遮蔽,业务又看不到任何有用信息。文档脱敏必须递归。
3.1 递归脱敏的核心策略
递归脱敏要处理四种结构单元:
| 结构单元 | 示例 | 脱敏动作 |
|---|---|---|
| 标量字段 | id_card字符串 | 按字段策略掩码/加密 |
| 嵌套对象 | buyer.contact | 递归进入子对象 |
| 数组 | items列表 | 逐个元素递归 |
| 自由文本 | note备注 | 正则抽取敏感片段后遮蔽 |
其中自由文本最麻烦:敏感信息藏在非结构化备注里,无法靠字段名识别。工程上需要"字段级规则 + 内容级识别"双管齐下——既认字段名,也认内容形态(手机号 11 位、身份证 18 位校验、邮箱@形态)。
3.2 嵌套文档递归脱敏算法
下面给出递归脱敏算法的伪代码,处理对象、数组、标量与文本四种情形(语义示意):
函数 recursive_mask(doc, policy, path=""): # doc: 当前处理的 BSON 节点(可能是对象/数组/标量/字符串) # policy: 字段路径到脱敏方法的映射 # path: 当前路径,用于匹配策略(如 buyer.contact.phone) if doc 是对象: out = 空对象 for key, val in doc: 子路径 = path + "." + key out[key] = recursive_mask(val, policy, 子路径) return out if doc 是数组: out = 空数组 for item in doc: out.append(recursive_mask(item, policy, path + "[]")) return out if doc 是字符串: # 1) 先看字段路径是否命中敏感类型 if path 匹配 policy.敏感路径: return mask_value(doc, policy[path]) # 2) 自由文本做内容级识别 if policy.启用文本识别: return mask_by_pattern(doc, [手机号正则, 身份证正则, 邮箱正则, 地址正则]) return doc # 非字符串标量(数字/布尔/日期)按路径策略处理或直接保留 if path 匹配 policy.敏感路径: return mask_value(doc, policy[path]) return doc # 入口 masked_doc = recursive_mask(raw_doc, policy)这个算法的要点在于path的累积拼接:无论嵌套多深、数组循环多少次,都能用buyer.contact.phone这种完整路径去匹配脱敏规则,从而做到"深度无关"的脱敏。同时文本识别作为第二道闸,兜住非结构化的敏感片段。
3.3 脱敏强度与可逆性的写法
在论证材料里描述文档脱敏强度时,建议按"方法 + 可逆性 + 残留风险"三段式书写,避免空泛。很多单位只写"已对 MongoDB 做脱敏",经不起追问:
- 方法:对嵌套文档按属性路径实施掩码与保留格式加密,敏感值不可逆遮蔽,关联键保真。
- 可逆性:授权业务主体经策略判定后可恢复明文,运维主体只能拿到脱敏值,且脱敏值不可逆还原。
- 残留风险:自由文本里的敏感片段可能因识别正则覆盖不全而残留,已通过多正则并行与人工抽检降低风险;数组元素递归保证了深度内的全覆盖。
这种写法比单纯写"已实现脱敏"扎实,评审方能从字里行间判断你真的做过威胁建模,而不是套模板。
3.4 脱敏前后对照
把上面的订单文档经过递归脱敏(运维视角)后,大致会变成:
| 原始路径 | 原始值 | 脱敏后(运维视角) |
|---|---|---|
| buyer.id_card | 110101199003071234 | 110101********1234 |
| buyer.contact.phone | 13812345678 | 138****5678 |
| buyer.contact.email | zhangsan@example.com | z***@example.com |
| ship_to.address | 北京市朝阳区xx路1号 | 北京市朝阳区**** |
| items[0].note | 备注含地址:北京市朝阳区xx路1号 | 备注含地址:北京市朝阳区**** |
可以看到,脱敏不是"整篇遮蔽",而是"逐字段、逐元素"精准处理,业务侧在授权后仍可拿到完整信息,运维侧则只能看到星号版本。这正是动态脱敏相对静态脱敏的优势:同一份数据、不同主体、不同视图。
四、查询审计:NoSQL 场景同样不能少
很多单位在关系库上已经建立了 SQL 级拦截与全量审计,但到了 Neo4j 的 Cypher、MongoDB 的聚合管道,审计就断了档。事实上图与文档的查询审计比关系库更关键——因为图遍历的扩散性,一条看似普通的查询可能把整张敏感子图拖出来。
4.1 要审计什么
NoSQL 审计至少应包含以下维度:
- 语句形态:被执行的 Cypher / 聚合管道 / 过滤条件;
- 遍历深度与波及面:图查询的跳数、返回的节点/边数量;文档查询命中的集合、文档数;
- 主体与来源:登录账号、应用标识、来源 IP(本地或远程接入);
- 命中策略:触发了哪条脱敏/拦截策略;
- 结果判定:返回明文、脱敏值,还是被拦截。
对这些要素做全量留痕,是内部数据泄露事后溯源与合规举证的基础。
4.2 高危查询的拦截思路
与运维管控网关对关系库SELECT *、全量导出做拦截同理,NoSQL 侧也应定义高危模式:
| 数据形态 | 高危模式 | 处置 |
|---|---|---|
| 图数据库 | 无起点约束的多跳全图遍历 | 限跳数或二次审批 |
| 图数据库 | 返回全部节点属性的*投影 | 强制投影白名单 |
| 文档数据库 | 无过滤条件的全集合扫描 | 限返回条数 |
| 文档数据库 | 带$match全量 +$lookup跨集联查 | 审批或限速 |
把这些规则与关系库的拦截规则收口到同一套管控平面,运维审计就能"一张图看全"。
五、与关系库脱敏能力的复用:以安当DBG为例
前面两节讲的是图与文档的脱敏方法,但这些方法并不是要另起炉灶。以安当DBG为例,它在关系库上已经沉淀了字段级加密、动态脱敏、权限三视图、运维管控与全量审计等能力,NoSQL 场景完全可以复用其"应用与数据库之间透明代理"的同一架构思想:
- 策略模型复用:关系库的"字段—规则—角色视图"三元组,平移到图里变成"属性路径—规则—角色视图",平移到文档里变成"文档路径—规则—角色视图"。策略表达语言可以保持一致,只是匹配维度从列名变成路径。
- 权限三视图复用:业务视图(授权见明文)、运维视图(见脱敏值)、拦截视图(高危直接拦),在图与文档上同样成立,只是判定发生在遍历产出与递归返回阶段。
- 审计平面复用:把 Cypher、聚合管道的审计记录与 SQL 审计记录汇入同一审计库,统一导出、统一举证。
以安当DBG为例,这种"同一管控平面、多数据形态"的设计,避免了为每个数据库类型各上一套脱敏系统,也避免了策略散落导致的治理盲区——这正是动态脱敏方案在混合数据架构下落地时最该坚持的一点:脱敏逻辑集中,数据形态分散。
5.1 应用零改造加密在 NoSQL 的延伸
关系库里"应用零改造加密"靠的是透明代理在协议层做加解密拦截。图与文档同样可以在驱动/协议层做文章:应用仍按原语义发 Cypher 或 BSON 查询,代理层负责在返回结果上做脱敏、在写入路径上做字段级加密。这样业务代码不用为脱敏改造,只是视图随主体身份变化——这与关系库上的应用零改造加密思路一脉相承。
六、性能:3 万 QPS 与 5%–10% 损耗在 NoSQL 是否成立
性能是脱敏网关绕不开的硬指标。图遍历和文档递归天然比"取列"更重,因为要在遍历/解析过程中逐节点、逐元素判定策略。那么关系库侧的基线还有参考意义吗?
6.1 损耗的来源分析
NoSQL 脱敏的额外开销主要来自三处:
- 路径计算:每到一个节点/字段都要累积路径并匹配策略,比关系库的列名匹配稍重;
- 结构遍历:图的多跳展开、文档的深度递归本身就是计算密集操作;
- 二次扫描兜底:结果态全量兜底扫描,理论上翻倍遍历一次。
但好消息是,这些开销都可以通过"策略预编译"“路径索引”"边/字段级短路"优化掉。策略预编译把buyer.contact.phone这样的路径规则编译成正则或前缀树,匹配成本降到微秒级;路径索引让引擎跳过非敏感子树,不去触碰不需要脱敏的字段。
6.2 如何把性能写进论证材料
以安当DBG为例,关系库侧的公开基线为单集群 3 万+ QPS、相对直连损耗 5%–10%。当把脱敏延伸到图与文档时,建议用同样的"前后对照"写法给出 NoSQL 的实测数字,而不是只给结论:
NoSQL 脱敏性能验证(语义示意) 1. 测试对象:图库代理(遍历态脱敏 + 结果态兜底) 2. 测试负载:3 跳社交关系遍历,平均返回 200 节点 / 400 边 3. 观测指标:QPS、平均延迟、P99 延迟 4. 结果: - 直连图库:QPS = 28,000,平均延迟 = 6.1ms - 经脱敏代理:QPS = 25,500,平均延迟 = 6.7ms - 损耗 ≈ 9%,处于 5%–10% 区间上沿,仍满足生产冗余 5. 文档库同理:嵌套深度 5 层、单文档 2KB,损耗约 7% 6. 结论:脱敏代理未成为图/文档访问的瓶颈在混合架构下给出"关系库、图库、文档库"三张基线对照,比单给一张关系库数字更有说服力,也能正面回应"加了脱敏层会不会拖垮图遍历"的质疑。
七、落地清单:从关系库到 NoSQL 的脱敏迁移
综合前文,把脱敏能力从关系库扩展到图与文档,建议按以下顺序推进:
- 先盘点图与文档里的敏感位置:用元数据扫描 + 内容识别,输出"属性路径清单"与"文档路径清单",而不是只扫表列。
- 统一策略语言:把"字段—规则—角色"模型升级为"路径—规则—角色",让关系、图、文档共用一套策略表达。
- 区分关联键与敏感值:图节点 ID、社群编号等关联属性保真,敏感属性按策略遮蔽或加密,保证图谱与分析可用。
- 图遍历态脱敏 + 结果态兜底:在遍历产出节点/边时实时脱敏,返回前全量扫描兜底,防止漏判。
- 文档递归脱敏:用路径累积的递归算法处理对象、数组、标量与自由文本,做到深度无关。
- 审计平面统一:Cypher、聚合管道、SQL 三类查询的审计汇入同一库,统一导出与举证。
- 性能基线补齐:对图与文档分别给出直连与经代理的 QPS、延迟、损耗对照,纳入整改材料。
7.1 一个常见踩坑:把脱敏做成"整篇遮蔽"
在文档场景,最省事的脱敏是"整篇打星",但这会废掉数据可用性;在图场景,最省事的是"不脱敏只控权限",但这又埋下内部数据泄露隐患。两者都是走极端。真正可用的脱敏方案必须在"可用"和"保密"之间取平衡点:保真关联键、精准遮蔽敏感值、按主体给视图。
7.2 远程接入场景的同等对待
和关系库一样,图与文档的远程接入通道也要走同一套脱敏代理,且策略与本地运维一致,仅把来源 IP 作为额外判定维度。很多单位内网脱敏做得好,但远程接入排查时直接返回完整敏感子图,等于把生产明文暴露在不控环境——这个坑在图数据库上尤其致命,因为一条遍历就能拉出整张关系网。
方案参考
图数据库与文档数据库的动态脱敏,本质是把关系库上已经成熟的"路径—规则—角色"“遍历/解析态脱敏 + 结果态兜底”"统一审计平面"等方法论,平移到非表结构的数据形态上,不依赖特定厂商。通用落地路径如下:
- 从列思维切换到路径思维:用属性路径、文档路径取代列名作为脱敏规则的挂载点,让策略深度无关、结构无关。
- 关联键与敏感值分离:图遍历与文档聚合依赖的关联属性保真,敏感属性按策略遮蔽或加密,保住数据可用性底线。
- 遍历/解析态脱敏优先,结果态兜底:在产出阶段实时脱敏,返回前再全量扫描,防止扩散途中漏判。
- 递归算法处理嵌套:用路径累积的递归函数统一处理对象、数组、标量与自由文本,自由文本辅以内容正则识别。
- 策略语言统一:关系、图、文档共用"路径—规则—角色"模型,避免为每种库各建一套脱敏系统导致治理盲区。
- 审计平面归一:把 Cypher、聚合管道、SQL 的查询审计汇入同一库,统一导出与举证,覆盖内部数据泄露的事后溯源。
- 性能用对照说话:对图与文档分别给出直连与经代理的 QPS、延迟、损耗基线,将其纳入论证材料,正面回应"脱敏是否拖累访问"的质疑。
- 远程接入同等对待:远程访问图与文档时同样走脱敏代理,与本地运维共用策略,堵住高频失分点。
对已经在关系库上部署了透明代理类方案的单位,可把上述八条逐项映射到现有策略模型,重点补齐"路径化规则"“关联键保真”"图/文档审计"三块,通常即可把脱敏防护从二维表平稳延伸到图与文档两种数据形态。