On the implementation of mobile phone number, ID number encryption and decryption solution
Require:
1.不入侵任何业务逻辑
2.可以扩展解密解密字段
3.可以扩展加密解密算法
environment:
jdk1.8、springMVC
solution:
基于自定义注解和mybatis自定义拦截器实现
Implementation:
自定义注解 @SensitiveData ,@SensitiveField
主要作用是:可以扩展任何想要加密的字段,只要判断加密的实体类上是否有这两个注解即可
/**
* 注解敏感信息类的注解
*/
@Inherited
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveData {}
/**
* 注解敏感信息类中敏感字段的注解
*/
@Inherited
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveField {}
Define the encryption and decryption interface for unified processing
package com.gisquest.platform.annotation;
import java.lang.reflect.Field;
public interface EncryptUtil {
/*declaredFields:传入加密注解修改的字段
* paramsObject:传入对应xml中paramterType对应的实例对象
*/
<T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException;
}
package com.gisquest.platform.annotation;
public interface DecryptUtil {
/*
* result:传入对应xml中resultType对应的实例对象
*/
<T> T decrypt(T result) throws IllegalAccessException;
}
Define the implementation class of the encryption and decryption interface for unified processing
@Component
public class EncryptProcessor implements EncryptUtil{
@Value(value="${encrypt.algorithmType}")
private String algorithmType;
@Override
public <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException {
for (Field field : declaredFields) {
//取出所有被注解的字段
SensitiveField sensitiveField = field.getDeclaredAnnotation(SensitiveField.class);
if (!Objects.isNull(sensitiveField)) {
field.setAccessible(true);
//获取该字段对应的value值
Object object = field.get(paramsObject);
//暂时只实现String类型的加密
if (object instanceof String) {
String value = (String) object;
if(!StringUtils.isEmpty(algorithmType)) {
field.set(paramsObject, CommonAlgorithmUtils.encrypt(algorithmType,value));
}
}
}
}
return paramsObject;
}
}
Define the implementation class of the decryption interface
@Component
public class DecryptProcessor implements DecryptUtil {
@Value(value="${decrypt.algorithmType}")
private String algorithmType;
@Override
public <T> T decrypt(T result) throws IllegalAccessException {
//取出resultType的类
Class<?> resultClass = result.getClass();
Field[] declaredFields = resultClass.getDeclaredFields();
for (Field field : declaredFields) {
//取出所有被EncryptDecryptField注解的字段
SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
if (!Objects.isNull(sensitiveField)) {
field.setAccessible(true);
Object object = field.get(result);
//只支持String的解密
if (object instanceof String) {
String value = (String) object;
if(!StringUtils.isEmpty(algorithmType)) {
//对注解的字段进行逐一解密
field.set(result, CommonAlgorithmUtils.decrypt(algorithmType,value));
}
}
}
}
return result;
}
}
Interceptor when defining encryption
package com.gisquest.platform.conf;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.Objects;
import java.util.Properties;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.springframework.core.annotation.AnnotationUtils;
import com.gisquest.platform.annotation.EncryptUtil;
import com.gisquest.platform.annotation.SensitiveData;
@Intercepts({ @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class) })
public class EncryptInterceptor implements Interceptor {
private EncryptUtil encryptUtil;
public EncryptInterceptor(EncryptUtil encryptUtil) {
this.encryptUtil = encryptUtil;
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
// 获取参数对像,即 mapper 中 paramsType 的实例
Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
parameterField.setAccessible(true);
// 取出实例
Object parameterObject = parameterField.get(parameterHandler);
if (parameterObject != null) {
Class<?> parameterObjectClass = parameterObject.getClass();
// 校验该实例的类是否被@SensitiveData所注解
SensitiveData sensitiveData = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveData.class);
if (Objects.nonNull(sensitiveData)) {
// 取出当前当前类所有字段,传入加密方法
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
encryptUtil.encrypt(declaredFields, parameterObject);
}
}
return invocation.proceed();
}
/**
* 切记配置,否则当前拦截器不会加入拦截器链
*/
@Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
// 自定义配置写入,没有自定义配置的可以直接置空此方法
@Override
public void setProperties(Properties properties) {
}
}
Interceptor when defining decryption
package com.gisquest.platform.conf;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Properties;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.CollectionUtils;
import com.gisquest.platform.annotation.DecryptUtil;
import com.gisquest.platform.annotation.SensitiveData;
@Intercepts({ @Signature(type = ResultSetHandler.class, method="handleResultSets", args=Statement.class) })
public class DecryptInterceptor implements Interceptor {
private final DecryptUtil decryptUtil;
DecryptInterceptor(DecryptUtil decryptUtil) {
this.decryptUtil = decryptUtil;
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 取出查询的结果
Object resultObject = invocation.proceed();
if (Objects.isNull(resultObject)) {
return null;
}
// 基于selectList
if (resultObject instanceof ArrayList) {
ArrayList<?> resultList = (ArrayList<?>) resultObject;
if (!CollectionUtils.isEmpty(resultList) && needToDecrypt(resultList.get(0))) {
for (Object result : resultList) {
// 逐一解密
decryptUtil.decrypt(result);
}
}
// 基于selectOne
} else {
if (needToDecrypt(resultObject)) {
decryptUtil.decrypt(resultObject);
}
}
return resultObject;
}
//判断是否需要解密
private boolean needToDecrypt(Object object) {
Class<?> objectClass = object.getClass();
SensitiveData sensitiveData = AnnotationUtils.findAnnotation(objectClass, SensitiveData.class);
return Objects.nonNull(sensitiveData);
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
@Intercepts: Identify this class as an interceptor
@Signature: Identify the interface to be intercepted, the interceptor that mybatis can extend
type: Identify the specific interface to be intercepted (ResultSetHandler.class,)
method: Identify the method in the interface to be intercepted (handleResultSets)
args: Identify the specific input parameters to intercept the interface method
Inject custom mybatis interceptors using configuration classes
@Configuration
public class IbatisInterceptorConfig{
@Value(value = "${encrypt.enable}")
public boolean isEnableEncrypt;
@Value(value = "${decrypt.enable}")
public boolean isEnableDecrypt;
@Autowired
EncryptProcessor encryptProcessor;
@Autowired
DecryptProcessor decryptProcessor;
@Autowired
ProcessEngineConfigurationImpl processEngineFactoryBean;
@PostConstruct
public void addInterceptor() {
//获取项目中的mybatis的SqlSessionFactory
SqlSessionFactory sqlSessionFactory=processEngineFactoryBean.getSqlSessionFactory();
if(isEnableEncrypt) {
sqlSessionFactory.getConfiguration().addInterceptor(new EncryptInterceptor(encryptProcessor));
}
if(isEnableDecrypt) {
sqlSessionFactory.getConfiguration().addInterceptor(new DecryptInterceptor(decryptProcessor));
}
}
}
configuration file:
The configuration in application.properties is as follows
encrypt.enable=true
encrypt.algorithmType=AES
decrypt.enable=true
decrypt.algorithmType=AES
Algorithm Unified Processing Tool
package com.gisquest.platform.common.utils;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import com.gisquest.platform.common.config.Global;
public class CommonAlgorithmUtils {
public static String encrypt(String algorithm,String content){
String result=StringUtils.EMPTY;
if(StringUtils.isEmpty(algorithm)) {
result=content;
return result;
}
if(!StringUtils.isEmpty(algorithm)) {
switch(algorithm){
case "AES":
result = AESUtils.encrypt(content, Global.getConfig("login.encryptKey"));
break;
case "RSA":
//使用时具体自己补充
PublicKey publicKey=null;
Cipher cipher=null;
try {
result=RsaUtils.encrypt(content, publicKey, cipher);
break;
} catch (InvalidKeyException | UnsupportedEncodingException | BadPaddingException
| IllegalBlockSizeException e) {
e.printStackTrace();
}
case "SM4":
result=SM4Utils.encryptEcb(content, SM4Utils.SECURE_LOG_KEY);
break;
default:
result = AESUtils.encrypt(content, Global.getConfig("login.encryptKey"));
}
}
return result;
}
public static String decrypt(String algorithm,String content){
String result=StringUtils.EMPTY;
if(StringUtils.isEmpty(algorithm)) {
result=content;
return result;
}
if(!StringUtils.isEmpty(algorithm)) {
switch(algorithm){
case "AES":
result = AESUtils.decrypt(content, Global.getConfig("login.encryptKey"));
break;
case "RSA":
//使用时具体自己补充
PrivateKey privateKey=null;
try {
result=RsaUtils.decrypt(content, privateKey);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
case "SM4":
//使用时具体自己补充
result=SM4Utils.decryptEcb(content, SM4Utils.SECURE_LOG_KEY);
break;
default:
result = AESUtils.decrypt(content, Global.getConfig("login.encryptKey"));
}
}
return result;
}
}
import com.gisquest.platform.annotation.SensitiveData;
import com.gisquest.platform.annotation.SensitiveField;
@SensitiveData
public class UserEntity {
private String id;
private String username;
@SensitiveField
private String staffName; // 用户姓名
//省略setter getter
}