代码连接【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文件引入依赖:
<!-- 依赖aop --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--配置密码加密--> <dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>3.0.4</version> </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); } }
-