专业的编程技术博客社区

网站首页 > 博客文章 正文

加密SpringBoot配置文件技巧(加密pdf文件如何解密编辑)

baijin 2024-11-13 09:32:23 博客文章 6 ℃ 0 评论



小试牛刀

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

<dependency>
????????<groupId>com.github.ulisesbocchio</groupId>
????????<artifactId>jasypt-spring-boot-starter</artifactId>
????????<version>3.0.2</version>
</dependency>

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

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

jasypt.encryptor.password=123abc
jasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128


@SpringBootTest
class?SpringbootPropertiesEncApplicationTests?{

????@Autowired
????private?StringEncryptor?stringEncryptor;

????@Test
????void?contextLoads()?{
????????String?sunshujie?=?stringEncryptor.encrypt("sunshujie");
????????String?qwerty1234?=?stringEncryptor.encrypt("qwerty1234");
????????System.out.println(sunshujie);
????????System.out.println(qwerty1234);
????}

}

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

jasypt.encryptor.password=123abc
jasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128
username=ENC(pXDnpH3GdMDBHdxraKyAt7IKCeX8mVlM9A9PeI9Ow2VUoBHRESQ5m8qhrbp45vH+)
password=ENC(qD55H3EKYpxp9cGBqpOfR2pqD/AgqT+IyClWKdW80MkHx5jXEViaJTAx6Es4/ZJt)

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

@SpringBootApplication
public?class?SpringbootPropertiesEncApplication?implements?CommandLineRunner?{
????private?static?final?Logger?logger?=?LoggerFactory.getLogger(SpringbootPropertiesEncApplication.class);
????public?static?void?main(String[]?args)?{
????????SpringApplication.run(SpringbootPropertiesEncApplication.class,?args);
????}

????@Value("${password}")
????private?String?password;
????@Value("${username}")
????private?String?username;

????@Override
????public?void?run(String...?args)?throws?Exception?{
????????logger.info("username:?{}?,?password:?{}?",?username,?password);
????}
}

原理解析

加密原理

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

jasypt.encryptor.password=123abc
jasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128

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文件入手查看自动配置类

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ulisesbocchio.jasyptspringboot.JasyptSpringBootAutoConfiguration

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

@Configuration
@Import({EnableEncryptablePropertiesConfiguration.class})
public?class?JasyptSpringBootAutoConfiguration?{
????public?JasyptSpringBootAutoConfiguration()?{
????}
}

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

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

2.注册了一个BeanFactoryPostProcessor -> EnableEncryptablePropertiesBeanFactoryPostProcessor

@Configuration
@Import({EncryptablePropertyResolverConfiguration.class,?CachingConfiguration.class})
public?class?EnableEncryptablePropertiesConfiguration?{
????private?static?final?Logger?log?=?LoggerFactory.getLogger(EnableEncryptablePropertiesConfiguration.class);

????public?EnableEncryptablePropertiesConfiguration()?{
????}

????@Bean
????public?static?EnableEncryptablePropertiesBeanFactoryPostProcessor?enableEncryptablePropertySourcesPostProcessor(ConfigurableEnvironment?environment)?{
????????boolean?proxyPropertySources?=?(Boolean)environment.getProperty("jasypt.encryptor.proxy-property-sources",?Boolean.TYPE,?false);
????????InterceptionMode?interceptionMode?=?proxyPropertySources???InterceptionMode.PROXY?:?InterceptionMode.WRAPPER;
????????return?new?EnableEncryptablePropertiesBeanFactoryPostProcessor(environment,?interceptionMode);
????}
}

先看EncryptablePropertyResolverConfiguration.class

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

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

jasypt.encryptor.property.prefix
jasypt.encryptor.property.suffix
????@Bean(
????????name?=?{"lazyEncryptablePropertyDetector"}
????)
????public?EncryptablePropertyDetector?encryptablePropertyDetector(EncryptablePropertyResolverConfiguration.EnvCopy?envCopy,?BeanFactory?bf)?{
????????String?prefix?=?envCopy.get().resolveRequiredPlaceholders("${jasypt.encryptor.property.prefix:ENC(}");
????????String?suffix?=?envCopy.get().resolveRequiredPlaceholders("${jasypt.encryptor.property.suffix:)}");
????????String?customDetectorBeanName?=?envCopy.get().resolveRequiredPlaceholders(DETECTOR_BEAN_PLACEHOLDER);
????????boolean?isCustom?=?envCopy.get().containsProperty("jasypt.encryptor.property.detector-bean");
????????return?new?DefaultLazyPropertyDetector(prefix,?suffix,?customDetectorBeanName,?isCustom,?bf);
????}

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

  • lazyEncryptablePropertyResolver 加密属性解析器
  • lazyEncryptablePropertyFilter 加密属性过滤器
????@Bean(
????????name?=?{"lazyEncryptablePropertyResolver"}
????)
????public?EncryptablePropertyResolver?encryptablePropertyResolver(@Qualifier("lazyEncryptablePropertyDetector")?EncryptablePropertyDetector?propertyDetector,?@Qualifier("lazyJasyptStringEncryptor")?StringEncryptor?encryptor,?BeanFactory?bf,?EncryptablePropertyResolverConfiguration.EnvCopy?envCopy,?ConfigurableEnvironment?environment)?{
????????String?customResolverBeanName?=?envCopy.get().resolveRequiredPlaceholders(RESOLVER_BEAN_PLACEHOLDER);
????????boolean?isCustom?=?envCopy.get().containsProperty("jasypt.encryptor.property.resolver-bean");
????????return?new?DefaultLazyPropertyResolver(propertyDetector,?encryptor,?customResolverBeanName,?isCustom,?bf,?environment);
????}

????@Bean(
????????name?=?{"lazyEncryptablePropertyFilter"}
????)
????public?EncryptablePropertyFilter?encryptablePropertyFilter(EncryptablePropertyResolverConfiguration.EnvCopy?envCopy,?ConfigurableBeanFactory?bf,?@Qualifier("configPropsSingleton")?Singleton<JasyptEncryptorConfigurationProperties>?configProps)?{
????????String?customFilterBeanName?=?envCopy.get().resolveRequiredPlaceholders(FILTER_BEAN_PLACEHOLDER);
????????boolean?isCustom?=?envCopy.get().containsProperty("jasypt.encryptor.property.filter-bean");
????????FilterConfigurationProperties?filterConfig?=?((JasyptEncryptorConfigurationProperties)configProps.get()).getProperty().getFilter();
????????return?new?DefaultLazyPropertyFilter(filterConfig.getIncludeSources(),?filterConfig.getExcludeSources(),?filterConfig.getIncludeNames(),?filterConfig.getExcludeNames(),?customFilterBeanName,?isCustom,?bf);
????}

再看EnableEncryptablePropertiesBeanFactoryPostProcessor这个类

  1. 是一个BeanFactoryPostProcessor
  2. 实现了Ordered,是最低优先级,会在其他BeanFactoryPostProcessor执行之后再执行
  3. postProcessBeanFactory方法中获取了上面提到的两个重要的bean, lazyEncryptablePropertyResolver lazyEncryptablePropertyFilter
  4. 从environment中获取了PropertySources
  5. 调用工具类进行转换PropertySources, 也就是把密文转换为原文
public?class?EnableEncryptablePropertiesBeanFactoryPostProcessor?implements?BeanFactoryPostProcessor,?Ordered?{????
????//?ignore?some?code
????public?void?postProcessBeanFactory(ConfigurableListableBeanFactory?beanFactory)?throws?BeansException?{
????????LOG.info("Post-processing?PropertySource?instances");
????????EncryptablePropertyResolver?propertyResolver?=?(EncryptablePropertyResolver)beanFactory.getBean("lazyEncryptablePropertyResolver",?EncryptablePropertyResolver.class);
????????EncryptablePropertyFilter?propertyFilter?=?(EncryptablePropertyFilter)beanFactory.getBean("lazyEncryptablePropertyFilter",?EncryptablePropertyFilter.class);
????????MutablePropertySources?propSources?=?this.environment.getPropertySources();
????????EncryptablePropertySourceConverter.convertPropertySources(this.interceptionMode,?propertyResolver,?propertyFilter,?propSources);
????}

????public?int?getOrder()?{
????????return?2147483547;
????}
}

再看工具类EncryptablePropertySourceConverter

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

2.转换为EncryptablePropertySource

3.用EncryptablePropertySource从PropertySources中替换原PropertySource

????public?static?void?convertPropertySources(InterceptionMode?interceptionMode,?EncryptablePropertyResolver?propertyResolver,?EncryptablePropertyFilter?propertyFilter,?MutablePropertySources?propSources)?{
????????((List)StreamSupport.stream(propSources.spliterator(),?false).filter((ps)?->?{
????????????return?!(ps?instanceof?EncryptablePropertySource);
????????}).map((ps)?->?{
????????????return?makeEncryptable(interceptionMode,?propertyResolver,?propertyFilter,?ps);
????????}).collect(Collectors.toList())).forEach((ps)?->?{
????????????propSources.replace(ps.getName(),?ps);
????????});
????}

关键方法在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. 解密
????public?String?resolvePropertyValue(String?value)?{
????????Optional?var10000?=?Optional.ofNullable(value);
????????Environment?var10001?=?this.environment;
????????var10001.getClass();
????????var10000?=?var10000.map(var10001::resolveRequiredPlaceholders);
????????EncryptablePropertyDetector?var2?=?this.detector;
????????var2.getClass();
????????return?(String)var10000.filter(var2::isEncrypted).map((resolvedValue)?->?{
????????????try?{
????????????????String?unwrappedProperty?=?this.detector.unwrapEncryptedValue(resolvedValue.trim());
????????????????String?resolvedProperty?=?this.environment.resolveRequiredPlaceholders(unwrappedProperty);
????????????????return?this.encryptor.decrypt(resolvedProperty);
????????????}?catch?(EncryptionOperationNotPossibleException?var5)?{
????????????????throw?new?DecryptionException("Unable?to?decrypt:?"?+?value?+?".?Decryption?of?Properties?failed,??make?sure?encryption/decryption?passwords?match",?var5);
????????????}
????????}).orElse(value);
????}

解惑

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

如果有其他疑惑欢迎留言提问, 另外由于作者水平有限难免有疏漏, 欢迎留言纠错。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表