抹掉就没法分析了——改写着送出去的手艺
一句话总结
日志必须发送到外部,但其中沾染着人和组织的信息。删掉的话无法分析,原样发出的话无法导出。介于两者之间的,就是确定性假名化——相同的值始终变成相同的假名,所以关联分析得以保留,而把假名还原为原始值的钥匙只留在内侧。
为什么需要它
在隔离网络的现场遇到解决不了的故障,最终还是要把日志发送到外部。无论是总部开发团队还是制造商,了解那份代码的人都在网络之外。但运营日志中原样包含着工号、账号、内部地址、终端标识符,以及人手写的备注。导出审查正是以这些内容为依据来退回的。
第一反应通常是掩码。把工号全部换成星号,把地址截掉。这样审查通过了,分析却通过不了。故障分析中最常用的事实,是“同一个用户在 3 分钟之内,同一个请求失败了五次”这类,而这个事实来自的不是值本身,而是值相同这一关系。全部变成同一个星号,五行就彼此成了陌生人;全部变成不同的随机数,看起来就像五个人各失败了一次。无论哪种,都找不到原因。
所以需要的是这样一个函数:相同的值变成相同的,不同的值变成不同的,而且无法还原。同时满足这三个条件的标准工具,就是带密钥的哈希,也就是 HMAC。其结构由 RFC 2104 定义,美国联邦标准用 FIPS 198-1 固定了同样的内容。在 Python 中,用标准库 hmac 一行就够了。
工作原理
第一,区分掩码与假名化。掩码是把值去掉,假名化是把值换掉。必须去掉的是无法清点里面有什么的位置。人随意书写的备注栏里,既可能出现电话号码,也可能出现姓名,无论写出什么样的正则表达式,下一个人都会用新的形式来书写。这样的栏位,整个删除更为诚实。反过来,由机器按固定模板生成的字段,因为知道会进来什么,所以做假名化。
第二,假名要挂在密钥上。不能直接用 sha256(사번) 来生成。工号的格式很窄,可能的值只有几万个,接收方把它们全部哈希并建成表,几秒钟就能还原。电话号码、身份证号分隔符也是同样的道理。混入密钥之后,不知道密钥的人就无法建立这张表。密钥不能随数据一起出去,而且密钥一变,假名也会整个改变,所以什么时候用了哪把密钥,要记录在内侧。
第三,单独统计正则表达式漏掉的位置。像 emp=E24-0101 这样带标签的位置很容易。困难的有三种:在日志消息正文里像句子一样嵌入的值、人写在自由叙述栏里的值,以及藏在 base64 这类编码背后的值。前两种靠按字段扫描绝对找不出来,第三种用任何正则表达式都找不出来。必须先解码再去查找。不统计这三项,就说“已用正则表达式全部处理”的那一刻,导出物就泄露了。
第四,查看删除之后仍然残留的风险。即使把直接标识符全部替换,把部门、职级和访问时间合在一起看,仍会留下可以缩小到一个人的行。这样的列叫作准标识符,具有相同组合的行少于 k 个的状态,被视为风险。降低风险的办法不是删除,而是让它变粗。把以分钟为单位的时间舍弃为以小时为单位,相同组合的行就会聚拢,唯一的行数随之减少。失去的是精度,得到的是可导出性,把这两者的交换用数字写下来,就是这一步的产出。美国标准机构关于去标识化的整理文档 NIST IR 8053 和 NIST SP 800-188 对这种交换有详细论述。
第五,映射表不放入导出物。把假名还原为原始值的表,对调查来说必不可少,但如果它随数据一起出去,就等于没有做假名化。表留在内侧的保险库中,对外只发出假名。日志管理的通用论述整理在 NIST SP 800-92 中。
在现场相遇的样子
有一次,导出审查被连续退回了两次。第一次是预料之中的理由——工号还在。改好后再次提交,第二次又被退回,理由是备注栏。有一行是经办人在“已通话”后面写下了联系方式,而这一行没有被我们写的任何一个正则表达式命中。从那以后,我们把整个删除自由叙述栏定为原则。如果分析确实需要,就让人重新阅读,只把需要的句子手工誊写过去。
另一个印象深刻的是编码。认证失败日志里的令牌片段是以 base64 写入的,解码之后发现是账号地址。在导出物中查找账号字符串的检查全部通过了,因为查找的一方只按原文去找。这就是为什么在编写自检时,不能只看“原来有的值在导出物中是否已经不存在”,还必须同时看“以编码形式是否也不存在”。
下一项实验要做什么
你将创建 96 行访问日志、16 行应用日志以及 18 人的名册,区分哪些字段是直接标识符、哪些是准标识符。把用正则表达式找到的值和漏掉的值分别统计,实现挂在密钥上的确定性假名函数,并用测试表明相同的值是否变成相同的假名,更换密钥后是否会不同。接着区分假名化与掩码分别应用,统计由准标识符组合而变得唯一的行,用泛化降低之后,由机器对导出包进行自检再导出。