持续创作,加速成长!这是我参与「掘金日新计划 · 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"
复制代码
注意:
- 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
复制代码
加密后的配置文件:
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运行:
- 命令行:
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密钥直接以明文的方式设置在配置文件中!