admin 管理员组文章数量: 1087652
springboot项目实现对加密数据自动解密
前段时间收到一个需求,需要对配置文件的加密数据在服务启动的时候进行自动解密,最主要的是要通用。理想的状态是别人直接引入jar,不需要对原有代码有任何改动就可以实现自动解密。
我们知道常规的加解密是通过在代码里面约定好(硬编码),对从配置文件读取出来的加密数据进行解密然后再使用的,每个项目需要解密都会有这样一段解密的代码,而且是硬编码进去的,不够灵活方便。既然要共用,主要是要解决两点问题:
一是要支持常规的加密方式。
二是要对原有项目要是无侵入的(如果要使用解密,只需要引入对应的jar)。
基于以上两点,解决方案也不复杂,针对第一点,我们可以内置常用的加密方式,并且针对对应加密方式指定规定的格式,使我们可以方便的辨别出到底使用的是哪种加密方式,然后按照对应的方式去解密。针对第二点,可以把 jar 做成自动装配的效果,只对满足我们条件的数据进行解密,由此也就诞生了这个小工具,闲话少说,开整!
因为考虑到使用的方便性,这里就使用对称加密方式,而常用的对称加密考虑到安全性等问题,这里选择AES和国密算法SM4(如果需要其他的算法可以自行扩展实现)。
上代码,先给出完整的 pom 文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns=".0.0"xmlns:xsi=""xsi:schemaLocation=".0.0 .0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.info</groupId><artifactId>custom-descriptor</artifactId><version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>5.1.5.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.5.RELEASE</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>compile</scope><version>1.18.20</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId><version>2.4.5</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.16</version></dependency><dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15to18</artifactId><version>1.66</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.32</version></dependency></dependencies><properties><mavenpiler.source>8</mavenpiler.source><mavenpiler.target>8</mavenpiler.target></properties></project>
主要是使用了国产开源库类hutool
,同时添加lombok
以及一些spring相关的jar
。
接下来是要两个加解密相关的工具类,主要是对 hutool 的简单封装,方便自己调用。
Sm4Util:
package com.info.descriptor.util;import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.symmetric.SM4;
import cn.hutool.crypto.symmetric.SymmetricCrypto;import static cn.hutool.crypto.Mode.CBC;
import static cn.hutool.crypto.Padding.ZeroPadding;public class Sm4Util {/*** 加密偏移量*/private static final String IV_PARAMETERS = "1a2b3c4d5e6f7g8h";public static String encrypt(String plainTxt, String key) {SymmetricCrypto sm4 = new SM4(CBC, ZeroPadding, key.getBytes(CharsetUtil.CHARSET_UTF_8), IV_PARAMETERS.getBytes(CharsetUtil.CHARSET_UTF_8));return Base64.encode(sm4.encrypt(plainTxt));}public static String decrypt(String cipherTxt, String key) {SymmetricCrypto sm4 = new SM4(CBC, ZeroPadding, key.getBytes(CharsetUtil.CHARSET_UTF_8), IV_PARAMETERS.getBytes(CharsetUtil.CHARSET_UTF_8));byte[] cipherHex = Base64.decode(cipherTxt);return sm4.decryptStr(cipherHex, CharsetUtil.CHARSET_UTF_8);}public static String generateSm4Key() {return RandomUtil.randomString(16);}public static String getIvParameters() {return IV_PARAMETERS;}
}
AesUtil:
package com.info.descriptor.util;import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.symmetric.AES;
import com.info.descriptor.enums.AesKeyTypeEnum;public class AesUtil {private static final String IV = "1a2b3c4d5e6f7g8h";private static final String AES_MODE = "CBC";private static final String AES_PADDING_STR = "PKCS7Padding";public static String generateRandomKey() {return generateRandomKey(AesKeyTypeEnum.KEY_TYPE_128);}public static String getIv() {return IV;}public static String generateRandomKey(AesKeyTypeEnum KeyType) {int keyLength;switch (KeyType) {case KEY_TYPE_192:keyLength = 192 / 8;break;case KEY_TYPE_256:keyLength = 256 / 8;break;case KEY_TYPE_128:default:keyLength = 128 / 8;break;}return RandomUtil.randomString(keyLength);}public static String encrypt(String data, String key, String iv) {AES aes = new AES(AES_MODE, AES_PADDING_STR, key.getBytes(), iv.getBytes());return aes.encryptHex(data);}public static String decrypt(String data, String key, String iv) {AES aes = new AES(AES_MODE, AES_PADDING_STR, key.getBytes(), iv.getBytes());return aes.decryptStr(data);}public static String encrypt(String data, String key) {AES aes = new AES(AES_MODE, AES_PADDING_STR, key.getBytes(), IV.getBytes());return aes.encryptHex(data);}public static String decrypt(String data, String key) {AES aes = new AES(AES_MODE, AES_PADDING_STR, key.getBytes(), IV.getBytes());return aes.decryptStr(data);}
}
AesKeyTypeEnum:
package com.info.descriptor.enums;public enum AesKeyTypeEnum {KEY_TYPE_128,KEY_TYPE_192,KEY_TYPE_256;
}
工具类都有了,接下来就是代码的主题,其实也就一个类,其核心思想是,通过spring的环境对象获取所有的配置信息,然后过滤对需要解密的值按指定方式进行解密,解密完成后覆盖原来读取到的值。
package com.info.descriptor;import com.info.descriptor.util.AesUtil;
import com.info.descriptor.util.Sm4Util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.*;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;/*** 和BeanPostProcessor原理一致,Spring提供了对BeanFactory进行操作的处理器BeanFactoryProcessor,* 简单来说就是获取容器BeanFactory,这样就可以在真正初始化bean之前对bean做一些处理操作。* 允许我们在工厂里所有的bean被加载进来后但是还没初始化前,对所有bean的属性进行修改也可以add属性值。*/
@Slf4j
public class Descriptor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered {private ConfigurableEnvironment environment;private String secretKey;private static final String DEFAULT_KEY_NAME = "secret.key";private static final String AES_REGEX = "^((?i)AES-)(\w+)((?i)-AES)$";private static final String SM4_REGEX = "^((?i)SM4-)([\w=]+)((?i)-SM4)$";@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {final Iterator<PropertySource<?>> iterator = this.environment.getPropertySources().iterator();while (iterator.hasNext()) {final PropertySource<?> source = iterator.next();if (source instanceof SimpleCommandLinePropertySource) {SimpleCommandLinePropertySource commandLinePropertySource = (SimpleCommandLinePropertySource) source;this.secretKey = commandLinePropertySource.getProperty(DEFAULT_KEY_NAME);break;}}if (!StringUtils.hasLength(this.secretKey)) {this.secretKey = !StringUtils.hasLength(System.getProperty(DEFAULT_KEY_NAME)) ? System.getenv(DEFAULT_KEY_NAME) : System.getProperty(DEFAULT_KEY_NAME);}if (!StringUtils.hasLength(this.secretKey)) {this.secretKey = environment.getProperty(DEFAULT_KEY_NAME);}if (!StringUtils.hasLength(this.secretKey)) {return;}log.info("this.secretKey = {}", this.secretKey);Map<String, Object> map = new ConcurrentHashMap<>();final Iterator<PropertySource<?>> propertyIterator = this.environment.getPropertySources().iterator();List<PropertySource<?>> propertySourceList = new ArrayList<>(1 << 3);while (propertyIterator.hasNext()) {propertySourceList.add(propertyIterator.next());}propertySourceList.stream().filter(propertySource -> propertySource instanceof EnumerablePropertySource).forEach(source -> this.processValue(map, (EnumerablePropertySource) source));if (!CollectionUtils.isEmpty(map)) {this.environment.getPropertySources().addFirst(new MapPropertySource("custom-decode", map));}}/*** @param map 处理完成后的键值对的存放集合* @param source 待处理的键值对*/private void processValue(Map<String, Object> map, EnumerablePropertySource source) {final String[] names = source.getPropertyNames();for (String name : names) {final Object value = source.getProperty(name);if (!(value instanceof String)) {continue;}String str = (String) value;if (match(AES_REGEX, str)) {map.put(name, AesUtil.decrypt(Patternpile(AES_REGEX).matcher(str).replaceAll("$2"), this.secretKey));}if (match(SM4_REGEX, str)) {map.put(name, Sm4Util.decrypt(Patternpile(SM4_REGEX).matcher(str).replaceAll("$2"), this.secretKey));}}}@Overridepublic void setEnvironment(Environment environment) {this.environment = (ConfigurableEnvironment) environment;}@Overridepublic int getOrder() {return Integer.MAX_VALUE;}private boolean match(String regex, String str) {if (!StringUtils.hasLength(regex) || !StringUtils.hasLength(str)) {return false;}Pattern pattern = Patternpile(regex);return pattern.matcher(str).matches();}
}
当然,为了是它能在被其他项目使用的时候自动生成,我们还需要把我们的解密器做成自动装配的效果,这点可以利用自定义spring-boot-starter的方式来实现。
首先新建一个配置类
package com.info.descriptor.config;import com.info.descriptor.Descriptor;
import com.info.descriptor.annotation.DecodeAutoConfigAnnotation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class DecodeAutoConfig {@Beanpublic static Descriptor descriptor() {return new Descriptor();}
}
resources目录下新建META-INF
文件夹,在其内新建 spring.factories
文件,在spring.factories
文件内添加如下内容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.info.descriptor.config.DecodeAutoConfig
至此,所有工作基本已完成,下面来测试看下效果:
新建springboot项目,首先引入我们写好的jar
<dependency><groupId>com.info</groupId><artifactId>custom-descriptor</artifactId><version>1.0-SNAPSHOT</version>
</dependency>
配置文件application.yml配置如下信息
secret:# 解密秘钥key: 8gsidftmog4851zttest:# 待解密的数据 AES加密的数据应该是 ‘AES-加密后的内容-AES’ 的形式,SM4加密的数据应该是 ‘SM4-加密后的内容-SM4’ 的形式# 当然 aes sm4也可以是小写字母key: AES-aaba5a242cd5002715b5531dac3b0562-AES
新建一个controller获取test.key
的进行测试
package com.example.demo.controller;import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class IndexController {@Value("${test.key}")private String key;@GetMapping("/index")public String index() {return this.key;}
}
打开浏览器,输入http://localhost:8080/index
,我们发现发挥的是已经解密的结果123test
。
当然,还可以给这个加解密做一个开关:
自定义注解:
package com.info.descriptor.annotation;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;import java.lang.annotation.*;@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ConditionalOnProperty(name = "decode.enabled",havingValue = "true")
public @interface DecodeAutoConfigAnnotation {
}
在配置类加上检查是否启用自动解密的注解:
package com.info.descriptor.config;import com.info.descriptor.Descriptor;
import com.info.descriptor.annotation.DecodeAutoConfigAnnotation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class DecodeAutoConfig {@Bean@DecodeAutoConfigAnnotationpublic static Descriptor descriptor() {return new Descriptor();}
}
这时,如果如果需要开启自动解密,需要在配置文件添加配置:
decode:# 开启自动解密enabled: true
至此,我们的需求已经完全实现了,有需要类似使用场景的的,可以参考这个demo来完成。
由于aes、sm4的具体加密方式可能存在差别(例如 是否对加密后的结果进行了base64编码等),必要的时候可能需要对配皮是否加密的正则进行调整,否则可能会出现匹配不到加密字符串的情况,匹配不到自然也就不会进行解密。
今天的内容就到这里了,由于笔者认知有限,文中肯定有描述错误的地方,希望大家多多包涵,及时指正,感谢。
本文标签: springboot项目实现对加密数据自动解密
版权声明:本文标题:springboot项目实现对加密数据自动解密 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/p/1700323376a396739.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论