使用Jasypt加密SpringBoot配置文件加密springboot配置文件

转自:Java知音 -sunshujie1990


1.构建一个springboot项目,并且引入jasypt依赖

<code><dependency>
<groupid>com.github.ulisesbocchio/<groupid>
<artifactid>jasypt-spring-boot-starter/<artifactid>
<version>3.0.2/<version>
/<dependency>/<code>

2.编写一个单元测试,用于获取加密后的账号密码

StringEncryptor是jasypt-spring-boot-starter自动配置的加密工具,加密算法我们选择PBEWithHmacSHA512AndAES_128,password为123abc

<code>jasypt.encryptor.password=123abc
jasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128/<code>


<code>@SpringBootTest
classSpringbootPropertiesEncApplicationTests{

@Autowired
privateStringEncryptorstringEncryptor;

@Test
voidcontextLoads(){
Stringsunshujie=stringEncryptor.encrypt("sunshujie");
Stringqwerty1234=stringEncryptor.encrypt("qwerty1234");
System.out.println(sunshujie);
System.out.println(qwerty1234);
}

}/<code>

3.在application.properties中配置加密后的账号密码

<code>jasypt.encryptor.password=123abc 

jasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128
username=ENC(pXDnpH3GdMDBHdxraKyAt7IKCeX8mVlM9A9PeI9Ow2VUoBHRESQ5m8qhrbp45vH+)
password=ENC(qD55H3EKYpxp9cGBqpOfR2pqD/AgqT+IyClWKdW80MkHx5jXEViaJTAx6Es4/ZJt)/<code>

4.观察在程序中是否能够拿到解密后的账号密码

<code>@SpringBootApplication
publicclassSpringbootPropertiesEncApplicationimplementsCommandLineRunner{
privatestaticfinalLoggerlogger=LoggerFactory.getLogger(SpringbootPropertiesEncApplication.class);
publicstaticvoidmain(String[]args){
SpringApplication.run(SpringbootPropertiesEncApplication.class,args);
}

@Value("${password}")
privateStringpassword;
@Value("${username}")
privateStringusername;

@Override
publicvoidrun(String...args)throwsException{
logger.info("username:{},password:{}",username,password);
}
}/<code>

原理解析

加密原理

首先看jasypt相关的配置,分别是password和加密算法

<code>jasypt.encryptor.password=123abc
jasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128/<code>

PBEWithHmacSHA512AndAES_128是此次我们选用的加密算法.

123abc是PBEWithHmacSHA512AndAES_128加密过程中用的加密密码.

PBE是基于密码的加密算法,密码和秘钥相比有什么好处呢?好处就是好记…

PBE加密流程如下

  1. 密码加盐
  2. 密码加盐结果做摘要获取秘钥
  3. 用秘钥对称加密原文,然后和盐拼在一起得到密文

PBE解密流程如下

  1. 从密文获取盐
  2. 密码+盐摘要获取秘钥
  3. 密文通过秘钥解密获取原文

再来看PBEWithHmacSHA512AndAES_128,名字就是加密过程中用的具体算法

  • PBE是指用的是PBE加密算法
  • HmacSHA512是指摘要算法,用于获取秘钥
  • AES_128是对称加密算法

jasypt-spring-boot-starter原理

先从spring.factories文件入手查看自动配置类

<code>org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ulisesbocchio.jasyptspringboot.JasyptSpringBootAutoConfiguration/<code>

JasyptSpringBootAutoConfiguration配置仅仅使用@Import注解引入另一个配置类EnableEncryptablePropertiesConfiguration.

<code>@Configuration
@Import({EnableEncryptablePropertiesConfiguration.class})
publicclassJasyptSpringBootAutoConfiguration{
publicJasyptSpringBootAutoConfiguration(){
}
}/<code>

从配置类EnableEncryptablePropertiesConfiguration可以看到有两个操作

1.@Import了EncryptablePropertyResolverConfiguration.class, CachingConfiguration.class

2.注册了一个BeanFactoryPostProcessor -> EnableEncryptablePropertiesBeanFactoryPostProcessor

<code>@Configuration
@Import({EncryptablePropertyResolverConfiguration.class,CachingConfiguration.class})
publicclassEnableEncryptablePropertiesConfiguration{
privatestaticfinalLoggerlog=LoggerFactory.getLogger(EnableEncryptablePropertiesConfiguration.class);

publicEnableEncryptablePropertiesConfiguration(){
}

@Bean
publicstaticEnableEncryptablePropertiesBeanFactoryPostProcessorenableEncryptablePropertySourcesPostProcessor(ConfigurableEnvironmentenvironment){
booleanproxyPropertySources=(Boolean)environment.getProperty("jasypt.encryptor.proxy-property-sources",Boolean.TYPE,false);
InterceptionModeinterceptionMode=proxyPropertySources?InterceptionMode.PROXY:InterceptionMode.WRAPPER;
returnnewEnableEncryptablePropertiesBeanFactoryPostProcessor(environment,interceptionMode);
}
}/<code>

先看EncryptablePropertyResolverConfiguration.class

lazyEncryptablePropertyDetector这里有配置文件中ENC()写法的出处.从名称来看是用来找到哪些配置需要解密.

从代码来看,不一定非得用ENC()把密文包起来, 也可以通过配置来指定其他前缀和后缀

<code>jasypt.encryptor.property.prefix
jasypt.encryptor.property.suffix/<code>
<code>@Bean(
name={"lazyEncryptablePropertyDetector"}
)
publicEncryptablePropertyDetectorencryptablePropertyDetector(EncryptablePropertyResolverConfiguration.EnvCopyenvCopy,BeanFactorybf){
Stringprefix=envCopy.get().resolveRequiredPlaceholders("${jasypt.encryptor.property.prefix:ENC(}");
Stringsuffix=envCopy.get().resolveRequiredPlaceholders("${jasypt.encryptor.property.suffix:)}");
StringcustomDetectorBeanName=envCopy.get().resolveRequiredPlaceholders(DETECTOR_BEAN_PLACEHOLDER);
booleanisCustom=envCopy.get().containsProperty("jasypt.encryptor.property.detector-bean");
returnnewDefaultLazyPropertyDetector(prefix,suffix,customDetectorBeanName,isCustom,bf);
}/<code>

另外还配置了很多bean,先记住这两个重要的bean.带着疑问往后看.

  • lazyEncryptablePropertyResolver 加密属性解析器
  • lazyEncryptablePropertyFilter 加密属性过滤器
<code>@Bean(
name={"lazyEncryptablePropertyResolver"}
)
publicEncryptablePropertyResolverencryptablePropertyResolver(@Qualifier("lazyEncryptablePropertyDetector")EncryptablePropertyDetectorpropertyDetector,@Qualifier("lazyJasyptStringEncryptor")StringEncryptorencryptor,BeanFactorybf,EncryptablePropertyResolverConfiguration.EnvCopyenvCopy,ConfigurableEnvironmentenvironment){
StringcustomResolverBeanName=envCopy.get().resolveRequiredPlaceholders(RESOLVER_BEAN_PLACEHOLDER);
booleanisCustom=envCopy.get().containsProperty("jasypt.encryptor.property.resolver-bean");
returnnewDefaultLazyPropertyResolver(propertyDetector,encryptor,customResolverBeanName,isCustom,bf,environment);
}

@Bean(
name={"lazyEncryptablePropertyFilter"}
)
publicEncryptablePropertyFilterencryptablePropertyFilter(EncryptablePropertyResolverConfiguration.EnvCopyenvCopy,ConfigurableBeanFactorybf,@Qualifier("configPropsSingleton")Singleton<jasyptencryptorconfigurationproperties>configProps){
StringcustomFilterBeanName=envCopy.get().resolveRequiredPlaceholders(FILTER_BEAN_PLACEHOLDER);
booleanisCustom=envCopy.get().containsProperty("jasypt.encryptor.property.filter-bean");
FilterConfigurationPropertiesfilterConfig=((JasyptEncryptorConfigurationProperties)configProps.get()).getProperty().getFilter();
returnnewDefaultLazyPropertyFilter(filterConfig.getIncludeSources(),filterConfig.getExcludeSources(),filterConfig.getIncludeNames(),filterConfig.getExcludeNames(),customFilterBeanName,isCustom,bf);
}/<jasyptencryptorconfigurationproperties>/<code>

再看EnableEncryptablePropertiesBeanFactoryPostProcessor这个类

  1. 是一个BeanFactoryPostProcessor
  2. 实现了Ordered,是最低优先级,会在其他BeanFactoryPostProcessor执行之后再执行
  3. postProcessBeanFactory方法中获取了上面提到的两个重要的bean, lazyEncryptablePropertyResolver lazyEncryptablePropertyFilter
  4. 从environment中获取了PropertySources
  5. 调用工具类进行转换PropertySources, 也就是把密文转换为原文
<code>publicclassEnableEncryptablePropertiesBeanFactoryPostProcessorimplementsBeanFactoryPostProcessor,Ordered{
//ignoresomecode
publicvoidpostProcessBeanFactory(ConfigurableListableBeanFactorybeanFactory)throwsBeansException{
LOG.info("Post-processingPropertySourceinstances");
EncryptablePropertyResolverpropertyResolver=(EncryptablePropertyResolver)beanFactory.getBean("lazyEncryptablePropertyResolver",EncryptablePropertyResolver.class);
EncryptablePropertyFilterpropertyFilter=(EncryptablePropertyFilter)beanFactory.getBean("lazyEncryptablePropertyFilter",EncryptablePropertyFilter.class);
MutablePropertySourcespropSources=this.environment.getPropertySources();
EncryptablePropertySourceConverter.convertPropertySources(this.interceptionMode,propertyResolver,propertyFilter,propSources);
}

publicintgetOrder(){
return2147483547;
}
}/<code>

再看工具类EncryptablePropertySourceConverter

1.过滤所有已经是EncryptablePropertySource的PropertySource

2.转换为EncryptablePropertySource

3.用EncryptablePropertySource从PropertySources中替换原PropertySource

<code>publicstaticvoidconvertPropertySources(InterceptionModeinterceptionMode,EncryptablePropertyResolverpropertyResolver,EncryptablePropertyFilterpropertyFilter,MutablePropertySourcespropSources){
((List)StreamSupport.stream(propSources.spliterator(),false).filter((ps)->{
return!(psinstanceofEncryptablePropertySource);
}).map((ps)->{
returnmakeEncryptable(interceptionMode,propertyResolver,propertyFilter,ps);
}).collect(Collectors.toList())).forEach((ps)->{
propSources.replace(ps.getName(),ps);
});
}/<code>

关键方法在makeEncryptable中,调用链路很长, 这里选取一条链路跟一下

  1. .ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter#makeEncryptable
  2. com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter#convertPropertySource
  3. com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter#proxyPropertySource
  4. com.ulisesbocchio.jasyptspringboot.aop.EncryptablePropertySourceMethodInterceptor#invoke
  5. com.ulisesbocchio.jasyptspringboot.caching.CachingDelegateEncryptablePropertySource#getProperty
  6. com.ulisesbocchio.jasyptspringboot.EncryptablePropertySource#getProperty()

看到最后豁然开朗,发现就是用的最开始配置的DefaultLazyPropertyResolver进行密文解析.

直接看最终的实现 DefaultPropertyResolver

  1. 据lazyEncryptablePropertyDetector过滤需要解密的配置
  2. 用lazyEncryptablePropertyDetector去掉前缀后缀
  3. 替换占位符
  4. 解密
<code>publicStringresolvePropertyValue(Stringvalue){
Optionalvar10000=Optional.ofNullable(value);
Environmentvar10001=this.environment;
var10001.getClass();
var10000=var10000.map(var10001::resolveRequiredPlaceholders);
EncryptablePropertyDetectorvar2=this.detector;
var2.getClass();
return(String)var10000.filter(var2::isEncrypted).map((resolvedValue)->{
try{
StringunwrappedProperty=this.detector.unwrapEncryptedValue(resolvedValue.trim());
StringresolvedProperty=this.environment.resolveRequiredPlaceholders(unwrappedProperty);

returnthis.encryptor.decrypt(resolvedProperty);
}catch(EncryptionOperationNotPossibleExceptionvar5){
thrownewDecryptionException("Unabletodecrypt:"+value+".DecryptionofPropertiesfailed,makesureencryption/decryptionpasswordsmatch",var5);
}
}).orElse(value);
}/<code>

解惑

1.加密配置文件能否使用摘要算法,例如md5?

不能, 配置文件加密是需要解密的,例如数据库连接信息加密,如果不解密,springboot程序无法读取到真正的数据库连接信息,也就无法建立连接.

2.加密配置文件能否直接使用对称加密,不用PBE?

可以, PBE的好处就是密码好记.

3.jasypt.encryptor.password可以泄漏吗?

不能, 泄漏了等于没有加密.

4.例子中jasypt.encryptor.password配置在配置文件中不就等于泄漏了吗?

是这样的,需要在流程上进行控制.springboot打包时千万不要把jasypt.encryptor.password打入jar包内.

在公司具体的流程可能是这样的:

  • 运维人员持有jasypt.encryptor.password,加密原文获得密文
  • 运维人员将密文发给开发人员
  • 开发人员在配置文件中只配置密文,不配置jasypt.encryptor.password
  • 运维人员启动应用时再配置jasypt.encryptor.password


分享到:


相關文章: