业务数据脱敏解决方案探究
superleeyom opened this issue · 0 comments
业务数据脱敏解决方案探究
使用背景
-
在实际的业务场景中,业务开发团队需要针对公司安全部门需求,针对涉及客户安全数据或者一些商业性敏感数据,如身份证号、手机号、银行卡号、客户号等个人信息,都需要进行数据脱敏。
-
搭建和生产环境一模一样的预发布环境,需要把生产环境的存量原文数据 加密后存储到预发环境。
技术调研
常见的脱敏算法
目前常见的脱敏算法包括 AES 加密、K 匿名、加星、屏蔽、洗牌、全保留、格式保留、令牌化等,算法及其主要用途介绍如下:
算法 | 主要用途 |
---|---|
K 匿名 | 通过如 K-匿名的算法对原始数据加入扰动和泛化,使得脱敏后的数据无法唯一对应回原数据,用于保留部分统计信息 |
加星 | 最基础的脱敏方法,保留字段一部分信息的同时通过加星遮蔽局部信息 |
屏蔽 | 用特殊字符如星号 * 作为掩码来将字段信息屏蔽掉 |
洗牌 | 将目标字段在样本中洗牌,使得脱敏后的信息无法唯一对应回原始样本,常用于脱敏后作为测试样例或保留部分统计信息 |
全保留 | 字段全保留,不脱敏,常用于字段无需脱敏的场景,如对主键不脱敏 |
格式保留 | 保留字段的原格式的前提下将其中一部分信息进行随机化,达到脱敏的同时仍然可以提供一部分该类型的信息,常用于脱敏后作为测试样例 |
令牌化 | 通过不可逆的算法将目标字段脱敏,但保留一份令牌使得可以在必要时还原 |
AES 加密 | 一种对称加密算法,必要时可通过 AESKey 对脱敏结果进行还原 |
Java生态下的脱敏方案
目前 Java 生态环境下,数据脱敏中间件这块,做的最好的应该数 ShardingSphere,引用 ShardingSphere 官方文档关于数据脱敏模块的说明:
数据脱敏模块属于ShardingSphere分布式治理这一核心功能下的子功能模块。它通过对用户输入的SQL进行解析,并依据用户提供的脱敏配置对SQL进行改写,从而实现对原文数据进行加密,并将原文数据(可选)及密文数据同时存储到底层数据库。在用户查询数据时,它又从数据库中取出密文数据,并对其解密,最终将解密后的原始数据返回给用户。
更多关于 ShardingSphere 的数据脱敏原理,可以查阅官方文档。
那如果说,我只是想简单的做一些数据清洗和隐私脱敏,有什么的好的工具类吗?那可以使用 hutool 的 DesensitizedUtil 工具类就行,例如:
String phone = "13488883888";
String encryptPhone = DesensitizedUtil.mobilePhone(phone);
// 打印:134****3888
System.out.println(encryptPhone);
技术演练
在 ShardingSphere 的关于数据脱敏的场景分析里,针对已上线的历史数据,需要业务方自己进行清洗。那怎么清洗?简单说下自己的想法:
-
首先数据库对那些需要脱敏的列,新增额外的加密列,比如需要对 email进行脱敏,则新建额外的加密列 encrypt_email。
-
系统接入 sharding-jdbc,并配置好脱敏规则。
-
写个脚本,多线程同时遍历需要脱敏的历史数据,将明文列(email)取出,更新到加密列(encrypt_email),由于sharding-jdbc 会根据脱敏规则,对SQL进行解析、改写,最后加密列存储的其实是加密后的数据。
这里将使用 ShardingSphere 的 sharding-jdbc 组件简单的演示如何进行数据脱敏。
数据库结构
user 表结构,其中 email 为明文列,encrypt_email 为密文列。
CREATE TABLE `user` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '姓名',
`age` int unsigned NOT NULL COMMENT '年龄',
`email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '明文邮箱 ',
`encrypt_email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '加密邮箱',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_email` (`email`) USING BTREE COMMENT '邮箱唯一索引'
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
配置解析
这里只展示 sharding-jdbc 的配置部分:
spring:
# sharding-jdbc配置
shardingsphere:
# 数据源配置
datasource:
name: ds
ds:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
jdbc-url: jdbc:mysql://127.0.0.1:3306/shardingsphere?useUnicode=true&useSSL=true&characterEncoding=utf8
username: root
password: '123456'
max-total: 100
encrypt:
# 加密器配置
encryptors:
# 加密器的名字,这里设置为:email_encryptor
email_encryptor:
# 加密方式,内置MD5/AES
type: aes
props:
# 配置AES加密器的KEY属性
aes.key.value: 123456abc
# 脱敏表配置
tables:
# 脱敏表名
user:
columns:
# 脱敏逻辑列,真实面向用户编写SQL
email:
# 存储明文列
plainColumn: email
# 存储加密列
cipherColumn: encrypt_email
# 使用的加密器
encryptor: email_encryptor
props:
# 是否使用密文列查询
query.with.cipher.column: true
# 是否打印SQL,默认false
sql.show: true
由于使用 ShardingSphere 的数据源 datasource 后,无需再配置 Spring 自带的 datasource,另外遇到个小问题就是,如果按照 ShardingSphere 官方关于数据脱敏的 springboot配置(官网示例 properties 格式,实际也可以转成yml 格式):
url: jdbc:mysql://127.0.0.1:3306/shardingsphere?useUnicode=true&useSSL=true&characterEncoding=utf8
会报错 jdbcUrl is required with driverClassName
,换成 jdbc-url
则正常启动:
jdbc-url: jdbc:mysql://127.0.0.1:3306/shardingsphere?useUnicode=true&useSSL=true&characterEncoding=utf8
实验对比
插入一条数据:
userService.save(new User("韩武江", 28, "hanwujiang@163.com"));
观察控制台打印的两条log:
# 逻辑SQL
Logic SQL: INSERT INTO user ( name,age,email ) VALUES ( ?,?,? )
# 实际SQL
Actual SQL: ds ::: INSERT INTO user ( name,age,encrypt_email, email ) VALUES (?, ?, ?, ?) ::: [韩武江, 28, dad+OyyGMYILeBGwiWHU+tmzk4uJ7CCz4mB1va9Ya1M=, hanwujiang@163.com]
再观察数据库:
加密列 encrypt_email 被 sharding-jdbc 加密存储。
查询数据,并设置 query.with.cipher.column: true
,开启密文列查询:
User user = userService.getByEmail("jianglanhe@gmail.com");
System.out.println(JSONUtil.toJsonStr(user));
观察控制台打印的log:
# 逻辑SQL
Logic SQL: SELECT id,name,age,email,encrypt_email FROM user WHERE email=?
# 实际SQL
Actual SQL: ds ::: SELECT id,name,age,encrypt_email AS email,encrypt_email FROM user WHERE encrypt_email=? ::: [dad+OyyGMYILeBGwiWHU+tmzk4uJ7CCz4mB1va9Ya1M=]
# 打印结果
{"encryptEmail":"dad+OyyGMYILeBGwiWHU+tmzk4uJ7CCz4mB1va9Ya1M=","name":"韩武江","id":13,"age":28,"email":"hanwujiang@163.com"}
若设置query.with.cipher.column: false
,关闭密文列查询:
观察控制台打印的log:
# 逻辑SQL
Logic SQL: SELECT id,name,age,email,encrypt_email FROM user WHERE email=?
# 实际SQL
Actual SQL: ds ::: SELECT id,name,age,email AS email,encrypt_email FROM user WHERE email=? ::: [hanwujiang@163.com]
# 打印结果
{"encryptEmail":"dad+OyyGMYILeBGwiWHU+tmzk4uJ7CCz4mB1va9Ya1M=","name":"韩武江","id":13,"age":28,"email":"hanwujiang@163.com"}
我原本以为在开启密文查询的情况,实际SQL都 encrypt_email AS email
了,最终返回实体里面的email应该是密文:
SELECT
id,
`name`,
age,
encrypt_email AS email,
encrypt_email
FROM
`user`
WHERE
encrypt_email = 'dad+OyyGMYILeBGwiWHU+tmzk4uJ7CCz4mB1va9Ya1M=';
但是对比可以发现,开启密文列查询配置后,实际sharding-jdbc会在中间把密文解密后返回,最终返回实体里面的email应该是解密后的明文,密文存在的还是在密文列 encrypt_email 中。
假如后期业务上线一段时间后,需要完全删除明文列,只保留密文列,在不改变代码的基础上是否可行?那直接在数据库层,把email列删除:
然后修改 sharding-jdbc 配置,去掉明文列,不改写任何其他代码层面的东西:
# 脱敏表配置
tables:
user:
columns:
email:
# plainColumn: email
cipherColumn: encrypt_email
encryptor: email_encryptor
然后查询数据:
// 为了演示,这里改用id查询,如果用email查询的话,肯定为空
User user = userService.getById(13);
System.out.println(JSONUtil.toJsonStr(user));
观察控制台,发现其实依然能正常执行,且不报错:
# 逻辑SQL
Logic SQL: SELECT id,name,age,email,encrypt_email FROM user WHERE id=?
# 实际SQL
Actual SQL: ds ::: SELECT id,name,age,encrypt_email AS email,encrypt_email FROM user WHERE id=? ::: [13]
# 打印结果
{"encryptEmail":"dad+OyyGMYILeBGwiWHU+tmzk4uJ7CCz4mB1va9Ya1M=","name":"韩武江","id":13,"age":28,"email":"dad+OyyGMYILeBGwiWHU+tmzk4uJ7CCz4mB1va9Ya1M="}
至于为什么会正常运行,因为:
因为有logicColumn存在,用户的编写SQL都面向这个虚拟列,Encrypt-JDBC就可以把这个逻辑列和底层数据表中的密文列进行映射转换
假如你删除 email列,但是配置中还配置了 plainColumn: email
,那代码执行的时候,则会报错:Cause: java.sql.SQLSyntaxErrorException: Unknown column 'email' in 'field list'
。
总结
我个人的感觉是 sharding-jdbc 确实做到了屏蔽底层对数据的脱敏处理 ,但是要接入 sharding-jdbc 的前提是,团队有制定严格的SQL规范 ,这样可能接入数据库中间件的时候,才会出现比较少的问题,对于一些老系统,动辄几百行的SQL,各种复杂函数,还是放弃接入的好,到时候只会是一步一个坑。
另外如果想要满足文章开头的第二个需求,也就是把生产库的数据同步到预发布,同时要屏蔽部分敏感数据,大部分的云厂商,都有提供脱敏工具,比如我们自己在用的腾讯云的 DBbrain,就可以支持数据脱敏,但是实际使用还不是怎么完善,有待改进。