实践:使用Jasypt加密SpringBoot配置文件加密springboot配置文件

简介: 实践:使用Jasypt加密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 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.过滤所有已经是EncryptablePropertySourcePropertySource


2.转换为EncryptablePropertySource


3.用EncryptablePropertySourcePropertySources中替换原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


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


END

相关文章
|
9月前
|
Java 数据安全/隐私保护
对称加密、非对称加密与哈希摘要
本内容介绍了对称加密、非对称加密和哈希摘要的基本概念与区别。对称加密使用同一密钥加解密,速度快但需妥善保管密钥;非对称加密使用公钥加密、私钥解密,安全性高但速度较慢;哈希摘要通过提取数据特征用于完整性校验,能有效区分不同数据。
284 2
|
10月前
|
数据安全/隐私保护
解释对称加密、非对称加密、哈希摘要
加密技术分为对称加密与非对称加密。对称加密使用同一密钥进行加解密,速度快但需严保管密钥;非对称加密则用公钥加密、私钥解密,安全性高但速度较慢。哈希摘要用于验证数据完整性,代表原始数据特征。
282 0
|
JSON 前端开发 Java
深入理解 Spring Boot 中日期时间格式化:@DateTimeFormat 与 @JsonFormat 完整实践
在 Spring Boot 开发中,日期时间格式化是前后端交互的常见痛点。本文详细解析了 **@DateTimeFormat** 和 **@JsonFormat** 两个注解的用法,分别用于将前端传入的字符串解析为 Java 时间对象,以及将时间对象序列化为指定格式返回给前端。通过完整示例代码,展示了从数据接收、业务处理到结果返回的全流程,并总结了解决时区问题和全局配置的最佳实践,助你高效处理日期时间需求。
2093 0
|
存储 Java 数据库
Spring Boot 注册登录系统:问题总结与优化实践
在Spring Boot开发中,注册登录模块常面临数据库设计、密码加密、权限配置及用户体验等问题。本文以便利店销售系统为例,详细解析四大类问题:数据库字段约束(如默认值缺失)、密码加密(明文存储风险)、Spring Security配置(路径权限不当)以及表单交互(数据丢失与提示不足)。通过优化数据库结构、引入BCrypt加密、完善安全配置和改进用户交互,提供了一套全面的解决方案,助力开发者构建更 robust 的系统。
442 0
|
9月前
|
安全 算法 Java
在Spring Boot中应用Jasypt以加密配置信息。
通过以上步骤,可以在Spring Boot应用中有效地利用Jasypt对配置信息进行加密,这样即使配置文件被泄露,其中的敏感信息也不会直接暴露给攻击者。这是一种在不牺牲操作复杂度的情况下提升应用安全性的简便方法。
1490 10
|
10月前
|
存储 安全 数据处理
探讨对称加密与非对称加密的区别
综上所述,对称加密和非对称加密的选用取决于不同的安全需求、性能考量和应用情境。了解各自的特点和限制,才能有效地部署合理的加密策略,以确保数据通信的安全性和效率。
1139 13
|
11月前
|
安全 Java 数据库
Jasypt加密数据库配置信息
本文介绍了使用 Jasypt 对配置文件中的公网数据库认证信息进行加密的方法,以提升系统安全性。主要内容包括:1. 背景介绍;2. 前期准备,如依赖导入及版本选择;3. 生成密钥并实现加解密测试;4. 在配置文件中应用加密后的密码,并通过测试接口验证解密结果。确保密码安全的同时,保障系统的正常运行。
809 3
Jasypt加密数据库配置信息
|
10月前
|
人工智能 安全 Java
Spring Boot yml 配置敏感信息加密
本文介绍了如何在 Spring Boot 项目中使用 Jasypt 实现配置文件加密,包含添加依赖、配置密钥、生成加密值、在配置中使用加密值及验证步骤,并提供了注意事项,确保敏感信息的安全管理。
1599 1
|
11月前
|
存储 运维 安全
OSS安全合规实战:金融行业敏感数据加密+KMS自动轮转策略(满足等保2.0三级要求)
金融行业OSS面临等保2.0、行业监管及数据泄露三重合规挑战,存在存储加密不足、密钥轮转滞后、访问控制不当等问题。本文提出分层加密架构,结合服务端KMS与客户端加密,设计自动密钥轮转机制,实现高性能与合规兼顾,并提供故障排查与成本优化方案,助力金融机构安全落地OSS应用。
612 1
|
JSON 前端开发 Java
深入理解 Spring Boot 中日期时间格式化:@DateTimeFormat 与 @JsonFormat 完整实践
在 Spring Boot 开发中,处理前后端日期交互是一个常见问题。本文通过 **@DateTimeFormat** 和 **@JsonFormat** 两个注解,详细讲解了如何解析前端传来的日期字符串以及以指定格式返回日期数据。文章从实际案例出发,结合代码演示两者的使用场景与注意事项,解决解析失败、时区偏差等问题,并提供全局配置与局部注解的实践经验。帮助开发者高效应对日期时间格式化需求,提升开发效率。
3787 2