使用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


分享到:


相關文章: