Spring Boot配置文件敏感信息加密

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情

一、背景

Spring Boot应用中Redis、数据库等的用户名、密码在配置文件中一般是明文存储,如果系统被攻破或者配置文件所在的目录读权限被破解,则黑客很容易通过配置文件获取到数据库的用户名和密码,进而达到非法连接数据库窃取数据的目的。

本文的目标是对配置文件的敏感信息加密,同时保持对现有应用的最小改动,对应用中配置文件中的密文配置项的使用保持和加密前一致,也就是使用配置项不受任何影响。

二、Spring Boot应用配置文件敏感信息加密

1. Jasypt

Jasypt 是一个通用的加解密方案,其特性:

  • 基于标准的加密算法,支持单向加密与反向加解
  • 适用于Spring应用,与SpringSecurity或Spring Boot可以实现无缝集成
  • 提供加密应用的配置文件的集成
  • 提供多处理器/多核系统中多线程的高性能加密功能
  • 开放与任何JCE(Java Cryptography Extension)实现相同的API

1.1 Jasypt spring boot

Jasypt spring boot是针对spring boot应用做的自动化配置的库,对配置文件的加解密提供强有力支持。

其原理是:

  • 通过spring.factories文件指定自动配置类:JasyptSpringBootAutoConfiguration和JasyptSpringCloudBootstrapConfiguration
  • 然后引入EnableEncryptablePropertiesBeanFactoryPostProcessor(本质是具有最高优先级的BeanFactoryPostProcessor),在Bootstrap阶段,对property(含配置项)做预先的解密处理。

本文着重介绍Jasypt spring boot对配置文件敏感项进行加解密功能和用法。

2. 依赖

含plugin,用于命令行对单字符串或配置文件中的敏感信息进行加解密

    <dependencies>
        <dependency>
            <groupId>com.github.ulise****occhio</groupId>
            <artifactId>jasypt-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>com.github.ulise****occhio</groupId>
                <artifactId>jasypt-maven-plugin</artifactId>
                <version>3.0.3</version>
            </plugin>
        </plugins>
    </build>
复制代码

3. 配置敏感信息加密

支持两种方式

3.1 单字符串加密后填入配置文件

加密

比如要把数据库用户名“root” 用密钥“123456”加密:

mvn jasypt:encrypt-value -Djasypt.encryptor.password="123456" -Djasypt.plugin.value="root"
复制代码

image.png 注意:

  • ENC()的括号中的是真正的密文
  • 加密的算法及其默认配置已经显示在图中,这些参数都可以定制

或者在配置文件中增加:

jasypt.encryptor.password=${JASYPT_ENCRYPTOR_PASSWORD:}
复制代码

然后可以通过配置环境变量这样用:

JASYPT_ENCRYPTOR_PASSWORD=123456 mvn jasypt:encrypt-value  -Djasypt.plugin.value="root"
复制代码

配置项加密后的密文可以直接替换到配置文件中,后续Jasypt可以读取并解密这些加密配置项,具体参考3.2节内容。

通过手工加密并替换配置文件中每一个加密项显然比较繁琐,推荐用3.2节方式。

解密

下面的命令将把密文解密还原为:“root”

mvn jasypt:decrypt-value -Djasypt.encryptor.password="123456" -Djasypt.plugin.value="ajUb0JNANCyRyyAPvBw59CtYwABRFuIGxakqRIatvqsWNeT4F332BupYFeRQhP8U"
复制代码

或者

JASYPT_ENCRYPTOR_PASSWORD=123456 mvn jasypt:decrypt-value -Djasypt.plugin.value="ajUb0JNANCyRyyAPvBw59CtYwABRFuIGxakqRIatvqsWNeT4F332BupYFeRQhP8U"
复制代码

3.2 对整个配置文件中的敏感信息做自动加密

3.2.1 配置文件中的敏感信息都用DEC()包起来

jasypt.encryptor.password=${JASYPT_ENCRYPTOR_PASSWORD:}
ssl.cert.path = DEC(/Users/yangjianguang/workspace/zhuojian/study/demo/dbdemo/src/main/resources/ssl)
ssl.config = DEC(useSSL=true&verifyServerCertificate=true&requireSSL=true&clientCertificateKeyStoreUrl=file:${ssl.cert.path}/keystoremysql&clientCertificateKeyStorePassword=password456&trustCertificateKeyStoreUrl=file:${ssl.cert.path}/truststoremysql&trustCertificateKeyStorePassword=password123)
spring.datasource.url = DEC(jdbc:mysql://192.168.1.48:3306/test?useUnicode=true&characterEncoding=utf-8&${ssl.config})
spring.datasource.username = DEC(sslx509)
spring.datasource.password = DEC(Test@123)
复制代码

3.2.2 执行配置文件加密

JASYPT_ENCRYPTOR_PASSWORD=123456 mvn jasypt:encrypt
复制代码

image.png 加密后的配置文件:

spring.application.name = dbdemo
http.port = 8080
jasypt.encryptor.password=${JASYPT_ENCRYPTOR_PASSWORD:}
ssl.cert.path = ENC(KKMPsPXSEfEK9E9Bl/MyaKaIRWPrb33kPCj/xxTuvWHue2sn5HYNHGj0UNEBP6YVlJm9hd6jkLMsme94pCfE1jcF764BBeprDZTapzJQk4WL6SO5p7cp3RkF7uXlSRtDZwEcco/5styjXYHbWJN6I4CPiUCZUfDW1flEfG53E5I=)
ssl.config = ENC(IApN3uvVdTpIx9NYsZbotfAVSt92BMxmMLRtO6d0Mbc8ZQZ0iEg2PFoDatEeqmKIO1/2gPAI+arRVQjTw9KTUjAiAS6CSnnswmyRGVcAkVJaR98Qc85DaG1XUwWCslwS24OLXzB0FyfWww8WQM6f8DmWACnygZyjt57agIKSRmHW188LlAk7xyfvdY8OdxXSWtUKWBcsJWMFvaCP/iRULnLBZ3rNEUcjHU8/iENGh1CkqoIyqwnmnCYPU4wVNtkfCaPvmmcTTBQJ66PaTZUqv5IrSPjNNPwUFgTq4JYoH1waB1Mg6OZOKeYgytxZbxR+XS1zvjSa3YD0hhLuira01ooKX49BJYUfjVQ6/dVmhN4+SSg71WOVRxpY9PLpNWi3AoXo/5PHgr0zttepqmGrrf5HKmvt6lb/noqqDafQdz4=)
spring.datasource.url = ENC(1PADaCLDniDQTfoNP0GBtNlqMtDKtOcwn3rii+IJ9TTaKhv83W1Zw8tRzc3s02Pu2n+OVzmznjwSSHRofO2ewkEEksM1BV5m4eAQYfM3W/Q5Z6ADYq0ICaao+Jt1bXk2uoCty0TKRTYF5/+qgVMMeO7Hs3ZX/9u59Q0UmTlC8PM=)
spring.datasource.username = ENC(NNZrW1I6j5/1XQNJAUyUqMz/ZYbmep07YXjMEI6RoNnm9Ogthn2cbvMFMAeMYDJ8)
spring.datasource.password = ENC(0Xr5ud/S4YLudpOlAYUbb4/NKuxFJa2N4CU2PfcqRVROfLUid3gNdSSYXBjO0Qw1)
复制代码

可见原来的明文都被自动加密了,而哪些非敏感信息,即没有用DEC()包起来的配置项保持原样。

可以通过spring.profiles.active或jasypt.plugin.path指定处理的配置文件:

mvn jasypt:encrypt -Dspring.profiles.active=cloud -Djasypt.encryptor.password="the password" 
mvn jasypt:encrypt -Djasypt.plugin.path="file:src/main/test/application.properties" -Djasypt.encryptor.password="the password"
复制代码

3.2.3 用加密后的配置文件运行Spring Boot应用

只需在启动时增加密钥配置!

  • IDEA运行:

image.png

  • 命令行:
mvn spring-boot:run -DskipTests -Djasypt.encryptor.password="123456"
复制代码

或者打成jar包后运行:

mvn package -DskipTests
JASYPT_ENCRYPTOR_PASSWORD=123456 java -jar target/dbdemo-0.0.1-SNAPSHOT.jar
复制代码

3.2.4 敏感配置字段使用

从前几节已经知道,我们通过命令行把配置文件中的敏感字段加密处理后,启动时只要提供密钥,在启动过程中jasypt会把加密的配置项解密,spring boot应用即可正常连接数据库,即原来的mybatis或druid怎么用配置文件中的配置信息连数据库,现在还是怎么用,不用做任何改动!

现在做个测试,新增Java Bean,在初始化后打印相关字段的值:

@Component
@Slf4j
public class JasyptDemo {

    @Value("${spring.datasource.url}")
    private String url;
    @Value("${spring.datasource.username}")
    private String username;
    @Value("${spring.datasource.password}")
    private String password;

    @PostConstruct
    public void init(){
        log.info("url: " + url);
        log.info("username:" + username + ", password: " + password);
    }
}
复制代码

运行后日志:

[14:22:15.474] INFO  com.example.dbdemo.JasyptDemo 26 init - url: jdbc:mysql://192.168.1.48:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true&verifyServerCertificate=true&requireSSL=true&clientCertificateKeyStoreUrl=file:/Users/yangjianguang/workspace/zhuojian/study/demo/dbdemo/src/main/resources/ssl/keystoremysql&clientCertificateKeyStorePassword=password456&trustCertificateKeyStoreUrl=file:/Users/yangjianguang/workspace/zhuojian/study/demo/dbdemo/src/main/resources/ssl/truststoremysql&trustCertificateKeyStorePassword=password123
[14:22:15.475] INFO  com.example.dbdemo.JasyptDemo 27 init - username:sslx509, password: Test@123
复制代码

这样就达到了:配置文件敏感信息加密,而程序中使用配置项不受任何影响的效果!

3.2.5 Jasypt普通加解密功能使用(非配置项加解密)

如果想使用Jasypt提供的普通加解密能力,可以直接引入bean:StringEncryptor使用,因不是本文重点,不具体展开了,参考如下代码:

@RestController
@Slf4j
public class DemoController {
    @Autowired
    private StringEncryptor stringEncryptor;

    @GetMapping(value="/enc")
    public String enc(@RequestParam("value") String value){
        return stringEncryptor.encrypt(value);
    }
    @GetMapping(value="/dec")
    public String dec(@RequestParam("value") String value){
        return stringEncryptor.decrypt(value);
    }
}
复制代码

3.2.6 自定义Jasypt加解密参数

有两种方式:配置文件和程序代码动态配置。由于配置文件配置项更容易被有心人利用,优先使用程序代码配置。

为了降低Jasypt密钥泄漏和被破解的风险,程序代码设置密钥还可以有多种手段:

  • 把密钥保存到特定文件中,放到系统特定目录下,并只允许特定用户读权限,程序从文件中读取密钥。
  • 程序从几个环境变量中读入并拼凑成密钥
  • 程序从专用密钥管理系统中读入密钥

Jasypt本身的配置除了最重要的密钥外,可以设置加解密算法及各种相关参数,具体见下面的配置项,详情可参考官网。

3.2.6.1 配置文件
Key Required Default Value
jasypt.encryptor.password True -
jasypt.encryptor.algorithm False PBEWITHHMACSHA512ANDAES_256
jasypt.encryptor.key-obtention-iterations False 1000
jasypt.encryptor.pool-size False 1
jasypt.encryptor.provider-name False SunJCE
jasypt.encryptor.provider-class-name False null
jasypt.encryptor.salt-generator-classname False org.jasypt.salt.RandomSaltGenerator
jasypt.encryptor.iv-generator-classname False org.jasypt.iv.RandomIvGenerator
jasypt.encryptor.string-output-type False base64
jasypt.encryptor.proxy-property-sources False false
jasypt.encryptor.skip-property-sources False empty list

注: jasypt.encryptor.pool-size是为了在多线程环境中高效加解密进行的设置,我们如果只是处理配置文件的加解密,设为1即可。

3.2.6.2 程序配置
@Configuration
public class JasyptConfiguration {
    @Primary
    @Bean("jasyptStringEncryptor")
    public StringEncryptor stringEncryptor() {
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();
        // 密钥可以从特定系统文件中读入,或从几个环境变量以一定规则拼凑,或从专门的密钥管理系统中获取密钥,目的都是降低密钥泄漏风险
        config.setPassword("123456");
        config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256");
        config.setKeyObtentionIterations("1000");
        config.setPoolSize("1");
        config.setProviderName("SunJCE");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
        config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
        config.setStringOutputType("base64");
        encryptor.setConfig(config);
        return encryptor;
    }
}
复制代码

三、后记

  • 配置文件中的所有敏感信息都应该加密,包括用户名、密码、证书路径和文件名、连接参数、第三方token、第三方接口地址等
  • Jasypt提供对配置文件无感的加解密功能,既能实现敏感信息加密,又对使用这些配置项的具体程序无侵入,是改动最小的推荐升级方案
  • Jasypt密钥需要妥善保管和传递,可以采用从特定系统文件读取、环境变量拼凑或者从专业密钥管理系统中获取等方式,尽量采用在代码中设置密钥的方式,禁止把Jasypt密钥直接以明文的方式设置在配置文件中!

猜你喜欢

转载自juejin.im/post/7110607743446188068