基于对称加密国密算法进行数据库存储加密
业务背景
某银行,由于行内要求以及系统安全考虑,现需要对数据库存储的敏感信息进行加密存储。
加密算法
加密算法分对称加密、非对称加密、可逆加密、不可逆加密。
- 对称加密:即加密和解密用同一个秘钥
- 非对称加密:秘钥分公钥和私钥,加密用私钥,解密需用公钥;加密用公钥,解密需用私钥。
- 可逆加密:即加密内容可解密
- 不仅可逆加密:即加密内容不可揭秘
根据业务背景来选去算法,因为数据在入库的时候需要加密,但在逻辑处理的时候需要解密,所以需要可逆加密算法。同时因为数据使用不存在第三方系统直接调用,所以选用对称加密。我这里选用的是国密SM4-ECB算法。
详细设计
分析
主流程业务节点按照订单号进行流转,订单号由日期+递增序号组成,切量阶段即可根据订单号进行判断是否需要加密或者解密。然后通过自定义注解,用来标识需要加解密的字段。这样就已经可以确认哪些数据哪些字段需要加密。然后新建一张秘钥参数表,用于存储秘钥和订单号的范围,这样就可以实现加密功能的切量。
由于加密后的内容是byte数组,长度为原数据长度向上取16的倍数,如果直接将byte数组存入数据库,会导致栏位长度扩大好几倍,增加数据库存储压力。所以在加密之后,对加密的结果转换为Base64后进行存储。转换后的长度大概为字节数组长度的三分之四。
设计
- 获取秘钥
对秘钥表创建缓存,根据订单号获取秘钥,获取不到即不需要加解密处理。 - 加解密
方法入参为泛型,利用反射,判断字段上是否有加解密的注解,以此来进行加解密,然后置入字段。
加密注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 是否加密
*
* @author LGQ
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IsEncrypt {
}
加密方法
/**
* 根据字段注解对实体进行加密
*
* @param t 实体
* @return 实体
*/
public static <T> T encrypt(T t, String secretKey) {
if (StringUtils.isBlank(secretKey)) {
return t;
}
String currentFiled = null;
String currentFiledValue = null;
try {
T newObject = (T) t.getClass().newInstance();
BeanUtils.copyProperties(t, newObject, t.getClass());
Class<?> aClass = t.getClass();
for (Field declaredField : aClass.getDeclaredFields()) {
declaredField.setAccessible(true);
IsEncrypt isEncrypt = declaredField.getDeclaredAnnotation(IsEncrypt.class);
if (null != isEncrypt) {
currentFiled = declaredField.getName();
currentFiledValue = (String) declaredField.get(t);
declaredField.set(newObject, encrypt(currentFiledValue, secretKey));
}
}
return newObject;
} catch (Exception e) {
log.error("SM4国密加密失败,加密秘钥为{},加密字段{},字段数据{}", secretKey, currentFiled, currentFiledValue, e);
throw new EncryptException("SM4国密加密失败");
}
}
/**
* 数据加密
*
* @param data 明文
* @return 加密后的byte转Base64
*/
public static String encrypt(String data, String secretKey) {
if (StringUtils.isBlank(data)) {
return data;
}
try {
//2、加密
SMCryptKY smCryptKY = new SMCryptKY();
byte[] secretDataBytes = smCryptKY.SM4EncryptWithECB(secretKey.getBytes(StandardCharsets.UTF_8), data.getBytes());
return Base64.getEncoder().encodeToString(secretDataBytes);
} catch (SMCryptException e) {
log.error("SM4国密加密失败,加密内容为{},加密秘钥为{}", data, secretKey, e);
throw new EncryptException("SM4国密加密失败");
}
}