mybatis数据加密组件组件
git地址:
http://10.16.202.103:8089/component/component-ser/mybatis-encrypt-starter
适用于在项目中使用mybatis-plus进行数据查询及保存的场景,使用 Mybatis
和 Mybatis-plus
的拦截器对数据进行加解密。
主要实现两个功能:
- 数据机密性保护:防止数据泄露,使用对字段进行加密的方式实现
- 数据完整性保护:防止数据被篡改,使用对数据计算消息摘要的方式实现
使用
引入依赖:
<dependency>
<groupId>com.gosci.tech</groupId>
<artifactId>mybatis-encrypt-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
在项目配置文件中添加配置,启动加解密配置,并选择具体算法:
encrypt:
enable: true
algorithm:
secrecy: aes
integrity: md5
上面这段配置,选择的是使用aes算法进行加解密,使用md5算法校验数据完整性。默认的实现是sm2和md5(可以不在配置文件中显式的指明)。本地测试过程中,因为无法连到dmz或金宏网,所以加密和签名只能使用这两种算法,切换到政务网后,需要将两者切换为gov模式。
更新:
本地测试推荐使用
aes
算法,因为aes算法对于相同内容多次加密的结果相同。如果对加密字段进行查询的话,每次加密结果不同的话会无法查询。
具体算法扩展可以看后面的部分,例如在使用政务云dmz区密评服务时,就需要把配置文件修改为:
encrypt:
enable: true
algorithm:
secrecy: gov
integrity: gov
properties:
# DMZ 使用这个地址
govApiHost: http://192.168.207.150:8880/miping
# 金宏网使用这个地址
# govApiHost: https://10.213.9.2:10006
appId: APP_C069372E7DB944258255A3DA001949FC
deviceID: DEV_57AA53A21ADC484E980EA43BB63A81D6
secret: E9CzOebl9qa0mYsAte6iWTC63RD09e0W
pstKey: "AQAAACAAAAB+PR2pafJ9DIBRzwaxyxuaeb3HDL9afPJr0/zLat4WlgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADuCb3Z8fF0GDOBbvdsHEDTilxGrIzeDBIdzDJ9d7fSkQ=="
keyId: 0253c934199f47adb2ac75bc5b0eb345
iv: Z29zY2lvY2VhbjEyMDAwMA==
注意,并不是在这里就对所有数据进行完整性和机密性保护了,如果需要做保护,需要在实体类或字段上添加对应注解,**这里只是确定加密算法使用什么 **。
建表语句
如果需要做完整性校验的话,需要在业务系统中添加一张新表 sys_data_hmac
,用来保存完整性校验的值:
-- mysql建表语句
CREATE TABLE `sys_data_hmac` (
`id` bigint(20) NOT NULL COMMENT '主键',
`table_name` varchar(64) DEFAULT NULL COMMENT '表名',
`data_id` bigint(20) DEFAULT NULL COMMENT '数据id',
`digest` varchar(256) DEFAULT NULL COMMENT '摘要',
`create_by` varchar(32) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(32) DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`del_flag` tinyint(2) NOT NULL DEFAULT '0' COMMENT '删除标志',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据摘要关联关系';
CREATE INDEX data_id_idx ON sys_data_hmac (`data_id`,`table_name`);
-- 达梦建表语句
CREATE TABLE "sys_data_hmac" (
"id" BIGINT IDENTITY(1,1) NOT NULL,
"table_name" VARCHAR(64) DEFAULT NULL NULL,
"data_id" BIGINT DEFAULT NULL NULL,
"digest" VARCHAR(256) DEFAULT NULL NULL,
"create_by" VARCHAR(32) DEFAULT NULL NULL,
"create_time" DATETIME DEFAULT NULL NULL,
"update_by" VARCHAR(32) DEFAULT NULL NULL,
"update_time" DATETIME DEFAULT NULL NULL,
"del_flag" TINYINT DEFAULT 0 NOT NULL,
PRIMARY KEY ("id")
);
CREATE INDEX SYS_DATA_HMAC_DATA_ID_IDX ON "sys_data_hmac" ("data_id","table_name");
完整性校验
在Po实体类上添加 @IntegrityCheck
注解,例如:
@Data
@IntegrityCheck
@TableName("system_dict")
public class SystemDictPo implements Serializable{
//...
}
一般,@IntegrityCheck
都会与加了 @TableName
的实体类配合使用,所以不需要添加额外的参数。如果在其他情况下使用时,需要添加一个参数:
@IntegrityCheck(tableName = "system_dict")
不参与签名的字段
数据库的通用字段实际业务意义不大,所以不需要进行签名计算,组件中预置了一些通用字段,在计算签名的过程中会自动去除调:
List<String> DEFAULT_FIELD =Arrays.asList("serialVersionUID",
"createBy","createTime","updateBy","updateTime","delFlag");
如果系统中还有其他的通用字段,可以在配置文件中的encrypt.properties.stopWord
中进行扩充,示例:
encrypt:
enable: true
properties:
stopWord:
- deleted
- createId
- createName
- updateId
- updateName
- remark
机密性校验
在需要加密的实体类字段上添加 @EncryptedColumn
注解,例如:
@EncryptedColumn
@ApiModelProperty(value = "字典描述")
@TableField("dict_desc")
private String dictDesc;
说明
在政府云密评项目改造中,要求机密性保护数据必须做完整性保护,所以在机密性保护的实体类中要添加上面两种注解。
但考虑到在其他情况下,数据可能不需要同时进行这两种保护,所以这两个注解可以分开使用,达到不同的保护目的。
数据初始化加密
全量初始化
项目启动后,直接调用内置接口 /data/encrypt/init
即可,数据量大的情况可能时间会比较长。
curl -X POST http://127.0.0.1:8888/data/encrypt/init
加密前建议先对加密列进行长度的扩展,达梦sql语句示例:
ALTER TABLE BOAT_NAVIGATION_EQUIPMENT.EQUIPMENT_INFO MODIFY EQUIPMENT_CODE VARCHAR(128) DEFAULT '' NOT NULL;
ALTER TABLE BOAT_NAVIGATION_EQUIPMENT.EQUIPMENT_INFO MODIFY BOAT_NUM VARCHAR(128) DEFAULT '' NOT NULL;
单张表初始化
如果某张表后续进行了调整,可以调用接口/data/encrypt/initPo
接口,只对这一张表再次进行初始化。请求方式:
curl http://127.0.0.1:10100/data/encrypt/initPo?poName=AuthUserPo
注意事项
查询参数中包含加密列的情况
分为两种情况,默认情况下,会有一个拦截器自动对参数进行加密,但是不排除复杂sql情况下可能会有问题。所以提供了两种方式供大家使用
1、自动加密方式
不需要做任何改动。
2、手动加密方式
需要对原有查询语句进行改造,改造前的语句:
SystemDictPo record = BeanUtil.copyProperties(systemDictDto, SystemDictPo.class);
List<SystemDictPo> list = this.list(record);
下面进行改造。
先注入SecrecyService
:
@Service
@RequiredArgsConstructor
public class SystemDictServiceImpl extends BaseServiceImpl<ISystemDictMapper, SystemDictPo> implements ISystemDictService {
private final SecrecyService secrecyService;
}
改业务查询代码:
SystemDictPo record = BeanUtil.copyProperties(systemDictDto, SystemDictPo.class);
// 手动方式,对参数手动加密
ManualSwitch.getInstance().setStatus(true);
record.setDictName(secrecyService.encrypt(record.getDictName()));
record.setDictDesc(secrecyService.encrypt(record.getDictDesc()));
List<SystemDictPo> list = this.list(record);
ManualSwitch.getInstance().remove();
或者,如果传的是Po的话,提供了方法直接对Po加密,可以简化为这样:
SystemDictPo record = BeanUtil.copyProperties(systemDictDto, SystemDictPo.class);
// 手动方式,对参数对象自动加密
ManualSwitch.getInstance().setStatus(true);
EntityUtil.encrypt(record);
List<SystemDictPo> list = this.list(record);
ManualSwitch.getInstance().remove();
注意,调用EntityUtil.encrypt()
这种方式传的参数一定要是Po,因为注解@EncryptedColumn
是加在Po上的,像Dto什么的是没有添加字段加密注解的。
目前存在的问题
saveBatch
因为mybatisplus的saveBatch()
方法无法获取到数据插入后的主键id,所以拦截器中无法进行hmac表的插入,如果有使用saveBatch()
的地方,目前只能暂时在循环中逐条插入。
线程池
测试过程中,有在线程池中执行sql,发现会有插入sys_data_hmac表失败的情况,需酌情决定是否使用线程池。
如果使用线程池,建议减小线程数量,最大线程数不要超过10
,创建线程池示例:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5,
10,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
new ThreadPoolExecutor.CallerRunsPolicy()
);
只能对字符串加密
因为目前加密时候用的都是反射,加密后的东西全是字符串类型,如果原类型不为字符串会产生异常。所以如果想对非字符串加密的话,需要先改数据库中的数据类型,再改java代码中的数据类型。
加密列不支持模糊查询
因为加密算法的原因,加密后无法进行模糊查询。如果想要对加密列实现模糊查询,需要自己扩展加密算法,可以参考:
只查询部分字段
例如下面这种情况,只查询一个字段:
queryWrapper.select("administrative_division");
List<OceanRanchBigScreenPo> list = this.list(queryWrapper);
查询出的Po中数据是不完整的,做完整性校验的时候肯定通不过,需要手动关闭校验:
SimplifySwitch.getInstance().setStatus(true);
queryWrapper.select("administrative_division");
List<OceanRanchBigScreenPo> list = this.list(queryWrapper);
SimplifySwitch.getInstance().remove();
数据库中设置了默认值
导致的问题:第一次插入时对象属性为null,插入数据库后数据库自己加了默认值,所以在下一次计算完整性时会报错。
解决方式:
- 删除数据库默认值
- 在代码中进行校验,为null时手动写入默认值
数值问题
Decimal类型会根据小数点后的精度自动补充0,导致插入和查询时计算hash时可能不一样,需要插入代码时手动设置精度。
扩展方式
扩展加解密算法
自己写一个类实现SecrecyService
接口,实现 encrypt
和 decrypt
方法。然后在 EncryptConfig
配置类中进行配置,需要指定一个启动的命名字符串。
以政务云加密方式GovSecrecyService
为例:
@Bean
@ConditionalOnProperty(value = "encrypt.algorithm.secrecy", havingValue = "gov")
public GovSecrecyService govSecrecyService() {
log.info("use gov secrecy algorithm");
return new GovSecrecyService();
}
扩展消息摘要算法
自己写一个类实现IntegrityService
接口,实现 digest
和 check
方法。然后在 EncryptConfig
配置类中进行配置,需要指定一个启动的命名字符串。
以政务云校验方式GovIntegrityService
为例:
@Bean
@ConditionalOnProperty(value = "encrypt.algorithm.integrity", havingValue = "gov")
public GovIntegrityService govIntegrityService() {
log.info("use gov integrity algorithm");
return new GovIntegrityService();
}