把沾着人的日志变成可以送出网外的样子
目标
从沾染着人和组织信息的运营日志中筛出标识符,区分应用挂在密钥上的确定性假名化与掩码,用泛化降低剩余的重新识别风险,然后由机器对导出包进行自检再导出。
为什么重要
在隔离网络中遇到解决不了的故障时,总有必须把日志发送到外部的时刻。这时如果用星号盖住工号,审查虽然通过,分析却死了。因为故障分析所使用的事实,大多不是值本身,而是值相同这一关系。所以需要的是一个始终把相同的值变成相同的假名、却无法还原的函数,而这个位置由带密钥的哈希来担任。本实验中困难的部分不是假名函数,而是清点什么是标识符。带标签的字段很容易,而在消息正文中像句子一样嵌入的值,以及藏在编码背后的值,会使导出物泄露。
步骤
- 用
python3在/root/deid/data中创建六个数据文件。直接使用生成脚本。 - 把每个字段的分类、唯一值数量和处理方式写入
/root/deid/classify.csv。 - 把正则表达式在字段中找到的值和漏掉的值写入
/root/deid/scan.json。 - 编写挂在密钥上的假名函数,留下
/root/deid/vault/map.csv和/root/deid/vault/proof.json。 - 区分假名化与掩码分别应用,生成
/root/deid/out/access.deid.log和/root/deid/out/app.deid.log。 - 统计由准标识符组合而变得唯一的行,写入
/root/deid/risk.json,并把泛化后的分析表留在/root/deid/out/events.csv。 - 把用原始标识符清单核对导出物的结果,写入
/root/deid/selfcheck.json。 - 在
/root/deid/release中创建只包含假名数据、方法说明和完整性校验的导出包。
参考
- 标识符模式、需要掩码的字段、编码字段、k 的基准值,全部在
/root/deid/data/policy.json中。不要把数字或正则表达式写死在代码里,而要从该文件中读取。 - 假名密钥在
/root/deid/data/key.hex中。这个文件留在网络内,不放入导出包。 - 假名是
HMAC-SHA256(열쇠, 종류 + "|" + 값)的十六进制输出的前pseudo_len个字符,再加上prefix。例如:EMP-0123456789ab。 - 转换顺序会改变结果。先对自由叙述字段做掩码,再把编码字段解码并换成假名,然后把剩余整行中命中模式的内容换成假名。
- 基准日在
/root/deid/data/asof.txt中。如果用date取今天,同样的数据每天都会得出不同的答案。 - 常见错误 1:只扫描带标签的字段。消息正文和编码的值,这样是找不出来的。
- 常见错误 2:把映射表一起放进导出包。如果这个文件进去了,就等于没有做假名化。
- 标准文档:RFC 2104、FIPS 198-1、NIST SP 800-92、NIST IR 8053。本实验的字段分类和 k 基准值不是标准的控制项编号,而是只在数据范围内使用的本实验的假设。
创建去标识化所用的数据
用 python3 在 /root/deid/data 中创建 access.log、app.log、roster.csv、policy.json、key.hex、asof.txt 六个文件。直接使用不含随机数的生成脚本。
隔离网络里没有可以下载的样本,所以先亲自创建数据。不使用随机数,才能保证无论谁运行多少次,得到的数据都相同,彼此的判定才能比对。评分器会把数据转换成标准形式并核对指纹,所以如果手工修改数据,后面的步骤会全部被卡住。
区分各个字段是什么
在 /root/deid/classify.csv 中,第一行写 field,category,distinct_values,action,然后为 policy.json 的每个 fields 各写一行。distinct_values 是该字段在数据中的唯一值数量,action 是 action_by_category 为该分类规定的处理方式。
分类是由组织规定的,所以 policy.json 中已经写好了。你要做的是附上与该分类相符的处理方式,并实际统计数据,填入唯一值数量。唯一值数量少的列,单独不能指向一个人,但与其他列合在一起就可以——这就是准标识符。
同时统计正则表达式找到的与漏掉的
在 /root/deid/scan.json 中写入 field_values(按类型统计的字段唯一值数量)、message_only(只存在于 msg 中、而不在任何字段中的值)、encoded(把 payload 用 base64 解码后得到的值)、note_only(只存在于 note 中的值)四项。后三项是排序后的列表。
带标签的位置,用 종류=값 来找。接下来是这一步的关键——单独扫描 msg 和 note 中双引号之间的内容,而 payload 要先解码再扫描。任何正则表达式都无法在原文状态下找到编码后的值。
创建挂在密钥上的确定性假名
在 /root/deid/vault/map.csv 中,第一行写 kind,value,pseudonym,为数据中找到的每个标识符各写一行;在 /root/deid/vault/proof.json 中写入 values、pseudonyms、collisions、stable、key_sensitive、alt_key_overlap 六项。
假名用 HMAC-SHA256 生成。把类型和值用竖线连接后放进去,即使工号和地址碰巧是同一个字符串,假名也会不同。测试有三项——同一个值计算两次是否相同,不同的值是否不会变成同一个假名,用 policy.json 中的另一把密钥计算时,假名集合是否完全没有重叠。这个保险库留在网络内。
区分应用假名化与掩码
创建 /root/deid/out/access.deid.log 和 /root/deid/out/app.deid.log。自由叙述字段整个替换成 policy.json 的 mask_token,编码字段替换成解码值的假名,其余模式全部替换成假名。行数和行的顺序必须与原始文件一致。
顺序会改变结果。如果不先做掩码,自由叙述中的值会被换成假名后留下来,该删除的东西就在没有被删除的情况下发了出去。编码字段要先解码,判定是哪种类型,再附上该类型的假名——同一个人必须与以原文出现的位置得到同样的假名。
统计把直接标识符全部替换后仍然残留的风险
在 /root/deid/risk.json 中写入 rows、k、groups_before、unique_before、groups_after、unique_after 六个数,并按访问日志的行顺序,在 /root/deid/out/events.csv 中留下以 pseudo_emp,dept,grade,hour 为第一行的泛化分析表。
准标识符组合写在 policy.json 的 quasi 中。部门和职级来自名册,时间来自访问日志。先按以分钟为单位的时间归组,统计相同组合的行少于 k 个的行,然后把时间舍弃为以小时为单位,再统计一次。减少的部分,就是靠泛化换来的安全,而失去的精度就是它的代价。
由机器核对导出物中是否还残留原始值
在 /root/deid/selfcheck.json 中写入 files_checked、forbidden_values、hits、pseudonyms_found 四个数。forbidden_values 是数据中全部标识符的数量,hits 是其中在导出物中被发现的笔数,必须为 0。
禁止清单比假名保险库更宽。被掩码去掉的值没有假名,但也不能出现在导出物中。连自由叙述中的值也要放进清单。核对的对象是 out 目录中的文件,而禁止清单和 selfcheck 结果要放在它之外——清单本身就是最危险的文件。
保险库留在内部,只导出包
在 /root/deid/release 中创建 access.deid.log、app.deid.log、events.csv 三个文件,以及 /root/deid/release/method.md、/root/deid/release/SHA256SUMS。方法说明有五个小节:## 무엇을 바꿨나、## 가명은 어떻게 만들었나、## 무엇을 지웠나、## 남은 위험、## 무결성,映射表和密钥不放入导出包。
接收方只会看到假名。所以,如果不同时附上说明哪些内容按什么规则被替换,这份数据就成了无法阅读的数据。完整性哈希要在文件最后一次修改之后再计算。并且要再用清单看一遍导出包里装了什么——把保险库一起放进去的事故最常见。