前言
数据脱敏
随着用户对个人隐私数据的重视和法律法规的完善,数据安全显得愈发重要。一方面可以加强权限管理,减少能够接触数据的人员以及导出数据加强审批。另一方面,还需要从技术上对用户隐私数据进行脱敏处理,提高数据的安全性。
一、数据脱敏方法
数据脱敏方法有很多种,大致可以按照以下进行分类:
- 隐藏法: 只显示敏感信息的部分内容,其他部分进行遮挡,比较常见使用星号替代。这种方式日常比较多见,比如手机号,银行卡号等只显示后面和后面几位,好处是虽然只是部分内容显示,但足够提供有效信息,同时不会暴露完整数据。
- 混淆法: 对原有数据截断、替换、隐藏、数字进行随机移位,使得原有数据完全失真或者部分失真,混淆真假。
- 加密:通过加密密钥和算法对敏感数据进行加密得到密文,密文可见但是完全没有可读意义,是脱敏最彻底的方法。其中对称加密还能密钥解密可以从密文恢复原始数据。比如密码保存采用非对称加密,手机号存储时采用对称加密。
用户的敏感数据包含姓名、电话号码、身份证、银行卡号、电子邮件、家庭住址、登录密码等等。需要考虑数据的敏感程度、数据安全要求以及实际业务使用场景选择合适的脱敏方法。Hutool包里面提供了许多常用的脱敏方法。
二、企业脱敏方案
企业如何实现脱敏?
我们先来看典型的系统数据交互链路,数据需要经过数据库、后端应用、前端(PC\移动端)。
- 数据库侧: 数据库保存了原始数据,有权限人员可以查看数据和导出数据。
- 后端应用内: 后端应用中会打印相关日志,数据通过日志存储下来。通过日志,能够得到原始数据。
- 应用输出: 前端能够从后端读取到原始数据。
1. 数据库脱敏方案
根据业务具体要求选择合适脱敏方法。
- 脱敏地点可以在应用中手动脱敏,当然这种方法不常用,改动点多对业务侵入大。
- 另外一种方案在ORM框架中修改sql实现,其中mybatis框架为java后端系统中最常用的框架。mybatis自带拦截器扩展,允许在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor: 拦截执行器的方法,例如 update、query、commit、rollback 等。可以用来实现缓存、事务、分页等功能。
- ParameterHandler: 拦截参数处理器的方法,例如 setParameters 等。可以用来转换或加密参数等功能。
- ResultSetHandler: 拦截结果集处理器的方法,例如 handleResultSets、handleOutputParameters 等。可以用来转换或过滤结果集等功能。
- StatementHandler: 拦截语句处理器的方法,例如 prepare、parameterize、batch、update、query 等。可以用来修改 SQL 语句、添加参数、记录日志等功能。
2. 历史数据脱敏
数据库脱敏另外一个问题是历史数据问题。历史原因最开始的技术方案保存明文,所以脱敏时需要做到平滑脱敏。要做到平滑脱敏,可按照如下流程:
- 新增脱敏字段: 在源表上新增脱敏字段。
- 数据双写: 源字段和脱敏字段都写入数据。
- 历史数据迁移: 历史数据迁移,刷入脱敏字段。
- 读取脱敏字段: 从脱敏字段读取数据返回。
- 清空源字段: 确保所有流程都正确的情况下,清空源字段。
3. 具体实现
本文mybatis实现数据库加解密为例。
1.表里面新增脱敏字段。
示例中脱敏新字段格式规范为源字段添加Encrypt后缀。vo里面添加脱敏注解标记。
public class Employee {
private Long id;
private String name;
@EncryptTag
private String mobile;
private String mobileEncrypt;
private String email;
private double salary;
}
2.实现自定义拦截。
/***
** 加密拦截
***/
@Intercepts({
@Signature(
type = Executor.class,
method = "update",
args = {
MappedStatement.class, Object.class}
), @Signature(
type = Executor.class,
method = "query",
args = {
MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
), @Signature(
type = Executor.class,
method = "query",
args = {
MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)})
public class EncryptPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0];
Object param = invocation.getArgs()[1];
PluginService.encrypt(invocation, param);
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
/***
** 解密拦截
***/
@Intercepts({
@Signature(
type = ResultSetHandler.class,
method = "handleResultSets",
args = {
Statement.class}
)})
public class DecryptPlugin implements Interceptor {
@Override
public Object intercep