代码连接【https://gitee.com/pengmqqq/sensitive-data-encryption】
后端敏感数据加密的一些解决方案,包括:
配置文件数据脱敏: Jasypt + AES
前后端传输以及数据库存储数据脱敏:AOP + AES
配置文件数据脱敏
将需要脱敏的数据进行加密之后再放入配置文件(注意要使用解密算法配套的加密算法)例如:
test:
password: Enc(tKWTx+XSlLlJFdLOIQPKYQ==)
前后端传输以及数据库存储数据脱敏:
在需要加密/解密的属性/参数上增加注解 @EncryptField
@Data
@Accessors(chain = true)
public class User {
private Integer id;
private String name;
@EncryptField
private String phone;
@EncryptField
private String email;
private Integer age;
}
在需要对参数加密的方法上增加注解 @NeedEncrypt
@NeedEncrypt
public void addAll(List<User> user) {
users.addAll(user);
System.out.println("");
}
在需要对返回值解密的方法上增加注解 @NeedDecrypt
例如某些需要访问第三方平台的操作,从数据库取到的是加密的数据,代码中需要进行解密再发送给第三方平台进行认证
@NeedDecrypt
public List<User> findAll() {
ArrayList<User> list = new ArrayList<>(users);
return list;
}
pom文件引入依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>com.github.ulisesbocchiogroupId>
<artifactId>jasypt-spring-boot-starterartifactId>
<version>3.0.4version>
dependency>
配置文件 application.yml 新增jasypt相关配置:
jasypt:
encryptor:
property:
# 算法识别的前后缀,默认ENC(),包含在前后缀的加密信息,会使用指定算法解密
prefix: Enc(
suffix: )
bean: desencrypt # 指定自定义加密算法的bean
test:
password: Enc(tKWTx+XSlLlJFdLOIQPKYQ==) # 加密数据
新增自定义算法类:
@Component("desencrypt")
public class JasyptAlgorithmConfig implements StringEncryptor {
@Override
public String encrypt(String message) {
return AESUtils.encrypt(message,AESUtils.getKey());
}
@Override
public String decrypt(String encryptedMessage) {
return AESUtils.decrypt(encryptedMessage,AESUtils.getKey());
}
新增加密工具类:
public class AESUtils {
private static final String USER_PWD_KEY = "A39DSSSDFGS4OaHr";
private static final Charset CHARSET = Charset.forName("UTF-8");
// 加密算法名称
private static final String AES = "AES";
// 偏移量-AES 128位数据块对应偏移量为16位字符串
private static final String IV = "70w5zbOds3DSFA5C";
// AES-加密方式, CBC-工作模式,PKCS5Padding-填充模式
private static final String AES_CBC_PKCS5PADDING = "AES/CBC/PKCS5Padding";
/**
* AES 加密操作
*
* @param content 待加密内容
* @param key 加密密钥
* @return 返回Base64转码后的加密数据
*/
public static String encrypt(String content, String key) {
if (StringUtils.isEmpty(content)) {
return content;
}
try {
/*
* 新建一个密码编译器的实例,由三部分构成,用"/"分隔,分别代表如下
* 1. 加密的类型(如AES,DES,RC2等)
* 2. 模式(AES中包含ECB,CBC,CFB,CTR,CTS等)
* 3. 补码方式(包含nopadding/PKCS5Padding等等)
* 依据这三个参数可以创建很多种加密方式
*/
Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5PADDING);
//偏移量
IvParameterSpec zeroIv = new IvParameterSpec(IV.getBytes(CHARSET));
byte[] byteContent = content.getBytes(CHARSET);
//使用加密秘钥
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(CHARSET), AES);
//SecretKeySpec skeySpec = getSecretKey(key);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, zeroIv); // 初始化为加密模式的密码器
byte[] result = cipher.doFinal(byteContent); // 加密
return Base64.encodeBase64String(result); //通过Base64转码返回
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* AES 解密操作
*
* @param content
* @param key
* @return
*/
public static String decrypt(String content, String key) {
if (StringUtils.isEmpty(content)) {
return content;
}
try {
Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5PADDING);
IvParameterSpec zeroIv = new IvParameterSpec(IV.getBytes(CHARSET));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(CHARSET), AES);
//SecretKeySpec skeySpec = getSecretKey(key);
cipher.init(Cipher.DECRYPT_MODE, skeySpec, zeroIv);
byte[] result = cipher.doFinal(Base64.decodeBase64(content));
return new String(result, CHARSET);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public static String getKey(){
return USER_PWD_KEY;
}
}
新增三个注解
@EncryptField
/**
* 安全字段注解
* 加在需要加密/解密的属性/参数上
* 实现自动加密解密
*/
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {
String[] value() default "";
}
@NeedDecrypt
/**
* 安全字段注解
* 加在需要解密的方法参数上
* 实现自动解密
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedDecrypt {
}
@NeedEncrypt
/**
* 安全字段注解
* 加在需要加密的方法参数上
* 实现自动加密
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedEncrypt {
}
新增两个切面类
EncryptAspect
/**
* @author 17540
* 对加了@NeedEncrypt注释的方法的参数进行扫描,参数中存在@EncryptFild修饰的加密字段,则进行加密
* 当前只适配非嵌套对象参数,List参数,普通String参数
*/
@Slf4j
@Aspect
@Component
public class EncryptAspect {
@Pointcut("@annotation(com.example.encryption.common.anno.NeedEncrypt)")
public void pointCut() {
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//加密
return joinPoint.proceed(encrypt(joinPoint));
}
public Object[] encrypt(ProceedingJoinPoint joinPoint) {
Object[] objects=null;
try {
objects = joinPoint.getArgs();
if (objects.length != 0) {
for (int i = 0; i < objects.length; i++) {
//抛砖引玉 ,可自行扩展其他类型字段的判断
if (objects[i] instanceof String) {
String value = encryptValue(objects[i]);
objects[i] = value;
} else {
encryptData(objects[i]);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return objects;
}
private void encryptData(Object obj) throws IllegalAccessException {
if (Objects.isNull(obj)) {
return;
}
if (obj instanceof ArrayList) {
encryptList(obj);
} else {
encryptObj(obj);
}
}
/**
* 加密对象
* @param obj
* @throws IllegalAccessException
*/
private void encryptObj(Object obj) throws IllegalAccessException {
if (Objects.isNull(obj)) {
log.info("当前需要加密的object为null");
return;
}
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
boolean containEncryptField = field.isAnnotationPresent(EncryptField.class);
if (containEncryptField) {
//获取访问权
field.setAccessible(true);
if (field.get(obj) == null) {
continue;
}
String value = AESUtils.encrypt(String.valueOf(field.get(obj)), AESUtils.getKey());
field.set(obj, value);
}
}
}
/**
* 针对list<实体来> 进行反射、解密
* @param obj
* @throws IllegalAccessException
*/
private void encryptList(Object obj) throws IllegalAccessException {
List<Object> result = new ArrayList<>();
if (obj instanceof ArrayList) {
result.addAll((List<?>) obj);
}
for (Object object : result) {
encryptObj(object);
}
obj = result;
}
/**
* 加密单个值
* @param realValue
* @return
*/
public String encryptValue(Object realValue) {
if (Objects.isNull(realValue)) {
return null;
}
try {
realValue = AESUtils.encrypt(String.valueOf(realValue), AESUtils.getKey());
} catch (Exception e) {
log.info("加密异常={}",e.getMessage());
}
return String.valueOf(realValue);
}
}
DecryptAspect
/**
* @author 17540
* 对加了@NeedEncrypt注释的方法的参数进行扫描,参数中存在@EncryptFild修饰的加密字段,则进行加密
* 当前只适配非嵌套对象参数,List参数,普通String参数
*/
@Slf4j
@Aspect
@Component
public class DecryptAspect {
@Pointcut("@annotation(com.example.encryption.common.anno.NeedDecrypt)")
public void pointCut() {
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//解密
Object result = decrypt(joinPoint);
return result;
}
public Object decrypt(ProceedingJoinPoint joinPoint) {
Object result = null;
try {
Object obj = joinPoint.proceed();
if (obj != null) {
//抛砖引玉 ,可自行扩展其他类型字段的判断
if (obj instanceof String) {
result = decryptValue(obj);
} else {
result = decryptData(obj);
}
}
} catch (Throwable e) {
e.printStackTrace();
}
return result;
}
private Object decryptData(Object obj) throws IllegalAccessException {
if (Objects.isNull(obj)) {
return null;
}
if (obj instanceof ArrayList) {
decryptList(obj);
} else {
decryptObj(obj);
}
return obj;
}
/**
* 针对单个实体类进行 解密
* @param obj
* @throws IllegalAccessException
*/
private void decryptObj(Object obj) throws IllegalAccessException {
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
boolean hasSecureField = field.isAnnotationPresent(EncryptField.class);
if (hasSecureField) {
field.setAccessible(true);
String realValue = (String) field.get(obj);
if (Objects.isNull(realValue)) {
continue;
}
try{
String value = AESUtils.decrypt(realValue, AESUtils.getKey());
field.set(obj, value);
log.info("解密后={}", value);
}catch (Exception e){
log.error("解密{}异常=,{}",realValue, e.getMessage());
}
}
}
}
/**
* 针对list<实体来> 进行反射、解密
* @param obj
* @throws IllegalAccessException
*/
private void decryptList(Object obj) throws IllegalAccessException {
List<Object> result = new ArrayList<>();
if (obj instanceof ArrayList) {
result.addAll((List<?>) obj);
}
for (Object object : result) {
decryptObj(object);
}
obj = result;
}
public String decryptValue(Object realValue) {
try {
realValue = AESUtils.decrypt(String.valueOf(realValue), AESUtils.getKey());
} catch (Exception e) {
log.info("解密{}异常={}",realValue, e.getMessage());
}
return String.valueOf(realValue);
}
}