您的位置 首页 java

加密SpringBoot配置文件技巧

加密SpringBoot配置文件技巧

没图不让发文章

小试牛刀

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 SpringbootPropertiesEnc application Tests {

    @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  LOG ger 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

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

文章来源:智云一二三科技

文章标题:加密SpringBoot配置文件技巧

文章地址:https://www.zhihuclub.com/179794.shtml

关于作者: 智云科技

热门文章

评论已关闭

2条评论

  1. If you are determined to try and conceive with your own eggs and have declining AMH levels and increasing FSH values DHEA may offer a novel way to restore egg quality and speed conception

  2. 24 Work is the integral of pressure over change in volume, and the pressure time product is the integral of pressure over inspiratory time

网站地图