# 逻辑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=';
// 为了演示,这里改用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="}
业务数据脱敏解决方案探究
使用背景
在实际的业务场景中,业务开发团队需要针对公司安全部门需求,针对涉及客户安全数据或者一些商业性敏感数据,如身份证号、手机号、银行卡号、客户号等个人信息,都需要进行数据脱敏。
搭建和生产环境一模一样的预发布环境,需要把生产环境的存量原文数据 加密后存储到预发环境。
技术调研
常见的脱敏算法
目前常见的脱敏算法包括 AES 加密、K 匿名、加星、屏蔽、洗牌、全保留、格式保留、令牌化等,算法及其主要用途介绍如下:
Java生态下的脱敏方案
目前 Java 生态环境下,数据脱敏中间件这块,做的最好的应该数 ShardingSphere,引用 ShardingSphere 官方文档关于数据脱敏模块的说明:
更多关于 ShardingSphere 的数据脱敏原理,可以查阅官方文档。
那如果说,我只是想简单的做一些数据清洗和隐私脱敏,有什么的好的工具类吗?那可以使用 hutool 的 DesensitizedUtil 工具类就行,例如:
技术演练
在 ShardingSphere 的关于数据脱敏的场景分析里,针对已上线的历史数据,需要业务方自己进行清洗。那怎么清洗?简单说下自己的想法:
首先数据库对那些需要脱敏的列,新增额外的加密列,比如需要对 email进行脱敏,则新建额外的加密列 encrypt_email。
系统接入 sharding-jdbc,并配置好脱敏规则。
写个脚本,多线程同时遍历需要脱敏的历史数据,将明文列(email)取出,更新到加密列(encrypt_email),由于sharding-jdbc 会根据脱敏规则,对SQL进行解析、改写,最后加密列存储的其实是加密后的数据。
这里将使用 ShardingSphere 的 sharding-jdbc 组件简单的演示如何进行数据脱敏。
数据库结构
user 表结构,其中 email 为明文列,encrypt_email 为密文列。
配置解析
这里只展示 sharding-jdbc 的配置部分:
由于使用 ShardingSphere 的数据源 datasource 后,无需再配置 Spring 自带的 datasource,另外遇到个小问题就是,如果按照 ShardingSphere 官方关于数据脱敏的 springboot配置(官网示例 properties 格式,实际也可以转成yml 格式):
会报错
jdbcUrl is required with driverClassName
,换成jdbc-url
则正常启动:实验对比
插入一条数据:
观察控制台打印的两条log:
再观察数据库:
加密列 encrypt_email 被 sharding-jdbc 加密存储。
查询数据,并设置
query.with.cipher.column: true
,开启密文列查询:观察控制台打印的log:
若设置
query.with.cipher.column: false
,关闭密文列查询:观察控制台打印的log:
我原本以为在开启密文查询的情况,实际SQL都
encrypt_email AS email
了,最终返回实体里面的email应该是密文:但是对比可以发现,开启密文列查询配置后,实际sharding-jdbc会在中间把密文解密后返回,最终返回实体里面的email应该是解密后的明文,密文存在的还是在密文列 encrypt_email 中。
假如后期业务上线一段时间后,需要完全删除明文列,只保留密文列,在不改变代码的基础上是否可行?那直接在数据库层,把email列删除:
然后修改 sharding-jdbc 配置,去掉明文列,不改写任何其他代码层面的东西:
然后查询数据:
观察控制台,发现其实依然能正常执行,且不报错:
至于为什么会正常运行,因为:
假如你删除 email列,但是配置中还配置了
plainColumn: email
,那代码执行的时候,则会报错:Cause: java.sql.SQLSyntaxErrorException: Unknown column 'email' in 'field list'
。总结
我个人的感觉是 sharding-jdbc 确实做到了屏蔽底层对数据的脱敏处理 ,但是要接入 sharding-jdbc 的前提是,团队有制定严格的SQL规范 ,这样可能接入数据库中间件的时候,才会出现比较少的问题,对于一些老系统,动辄几百行的SQL,各种复杂函数,还是放弃接入的好,到时候只会是一步一个坑。
另外如果想要满足文章开头的第二个需求,也就是把生产库的数据同步到预发布,同时要屏蔽部分敏感数据,大部分的云厂商,都有提供脱敏工具,比如我们自己在用的腾讯云的 DBbrain,就可以支持数据脱敏,但是实际使用还不是怎么完善,有待改进。
示例代码:shardingsphere-encrypt-jdbc-demo