第 48 章:Jasypt 配置加密
学习目标
- 理解配置明文的安全风险
- 掌握 Jasypt 集成与使用
- 了解密钥管理的最佳实践
一、为什么要加密配置?
yaml
# ❌ 明文密码提交到 Git
spring:
datasource:
password: Prod@Db#2024
redis:
password: RedisSecret123风险清单:
| 风险 | 后果 |
|---|---|
| Git 历史永久保留 | 即使后来删了,git log -p 还能翻出来 |
| 仓库泄露 | 私仓变公开、离职员工有 clone、外包接触过代码 |
| 运维人员可见 | 服务器上 cat application.yml 就能看到 |
| 无法审计 | 谁看过、谁改过密码,无迹可查 |
真实案例:GitHub 上每天都有扫描机器人在找泄露的密钥。AWS 密钥泄露后几分钟内就会被拿去挖矿。
二、Jasypt 集成
引入依赖
xml
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>配置
yaml
jasypt:
encryptor:
password: ${JASYPT_PASSWORD} # ① 主密钥:从环境变量读,绝不写在文件里
algorithm: PBEWITHHMACSHA512ANDAES_256 # ② 强算法
iv-generator-classname: org.jasypt.iv.RandomIvGenerator
property:
prefix: "ENC(" # ③ 密文标识(默认就是这个)
suffix: ")"使用
yaml
spring:
datasource:
username: root
password: ENC(G6N718UuyPE5bHyWKyuLQSm02auQPUtm) # ✅ 密文Spring 启动时会自动识别
ENC(...)包裹的值,用主密钥解密后再注入。业务代码完全无感知。
三、生成密文
方式 1:命令行(推荐,不污染代码)
bash
java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI \
input="Prod@Db#2024" \
password="你的主密钥" \
algorithm=PBEWITHHMACSHA512ANDAES_256 \
ivGeneratorClassName=org.jasypt.iv.RandomIvGenerator输出:
----OUTPUT----------------------
G6N718UuyPE5bHyWKyuLQSm02auQPUtm方式 2:写个工具类(本地用)
java
public class JasyptTool {
public static void main(String[] args) {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword("你的主密钥"); // ①
config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256");
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
// ② 加密
String encrypted = encryptor.encrypt("Prod@Db#2024");
System.out.println("密文: ENC(" + encrypted + ")");
// ③ 解密验证
System.out.println("明文: " + encryptor.decrypt(encrypted));
}
}⚠️ 这个工具类不要提交到 Git(主密钥在里面)。放在
src/test/java下,或者加到.gitignore。
💡 注意:同一个明文每次加密结果都不同(因为用了随机盐 + 随机 IV),但都能正确解密。这是正确行为,不是 bug——固定输出会让攻击者能做字典比对。
四、主密钥怎么管理?
这是整个方案的核心难点:配置加密了,但解密用的主密钥放哪?
✅ 方案 1:环境变量(中小项目推荐)
bash
# Linux 服务器
export JASYPT_PASSWORD='主密钥'
java -jar app.jar
# systemd
[Service]
Environment="JASYPT_PASSWORD=主密钥"
# Docker
docker run -e JASYPT_PASSWORD=主密钥 taskflow:1.0
# Kubernetes(用 Secret)
env:
- name: JASYPT_PASSWORD
valueFrom:
secretKeyRef:
name: taskflow-secret
key: jasypt-password❌ 方案 2:启动参数(不推荐)
bash
java -jar app.jar --jasypt.encryptor.password=主密钥为什么不行? 服务器上任何用户执行
ps aux | grep java都能看到完整命令行,包括密钥。
✅ 方案 3:外部文件 + 严格权限
bash
echo "主密钥" > /etc/taskflow/.jasypt-key
chmod 400 /etc/taskflow/.jasypt-key # 只有属主可读
chown appuser:appuser /etc/taskflow/.jasypt-keyjava
@Bean("jasyptStringEncryptor")
public StringEncryptor stringEncryptor() throws IOException {
String key = Files.readString(Path.of("/etc/taskflow/.jasypt-key")).trim();
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(key);
config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256");
config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
encryptor.setConfig(config);
return encryptor;
}⭐ 方案 4:云 KMS / Vault(大型项目)
| 平台 | 服务 |
|---|---|
| AWS | KMS + Secrets Manager |
| 阿里云 | KMS |
| 通用 | HashiCorp Vault |
| K8s | Sealed Secrets / External Secrets Operator |
优势:密钥永不落盘、自动轮换、完整审计日志、细粒度权限控制。
五、需要加密哪些配置?
| 必须加密 | 不需要加密 |
|---|---|
| 数据库密码 | 数据库连接池大小 |
| Redis 密码 | 服务端口 |
| MQ 密码 | 日志级别 |
| 第三方 API Secret(支付、短信、OSS) | 超时时间 |
| JWT 签名密钥 | 功能开关 |
| 加密盐值 | 域名(内网 IP 建议也加密) |
六、Jasypt 的局限与替代方案
| 局限 | 说明 |
|---|---|
| 仍是对称加密 | 拿到主密钥就能解密所有配置 |
| 无法轮换 | 换主密钥要重新加密所有配置项 |
| 无审计 | 不知道谁在什么时候解密了什么 |
| 主密钥仍需保护 | 只是把「保护 N 个密码」变成「保护 1 个密钥」 |
准确定位:Jasypt 解决的是「配置文件不能出现明文密码」这一个问题,它不是完整的密钥管理方案。
更好的选择
yaml
# ① 配置中心(Nacos/Apollo):密码存在配置中心,有权限和审计
spring:
cloud:
nacos:
config:
server-addr: nacos:8848
# ② K8s Secret:密码作为环境变量注入,代码里完全没有
spring:
datasource:
password: ${DB_PASSWORD}
# ③ 云数据库 IAM 认证:连数据库都不用密码,用临时 token优先级建议:K8s Secret / 云 IAM > 配置中心 > Jasypt > 明文。
七、避坑清单
yaml
# ❌ 坑 1:主密钥写在配置文件里 —— 等于没加密
jasypt:
encryptor:
password: mySecretKey # ❌ 密钥和密文放在一起
# ❌ 坑 2:用弱算法(Jasypt 2.x 默认的 PBEWithMD5AndDES 已不安全)
jasypt:
encryptor:
algorithm: PBEWithMD5AndDES # ❌ 用 PBEWITHHMACSHA512ANDAES_256
# ❌ 坑 3:yml 里密文没引号,含特殊字符时解析出错
password: ENC(abc+/=def) # ⚠️ 建议加引号
password: "ENC(abc+/=def)" # ✅bash
# ❌ 坑 4:历史 Git 记录里还有明文
# 加密只对新提交有效,历史记录必须清理:
git filter-repo --path application-prod.yml --invert-paths
# 然后:立即在数据库/第三方平台上把泄露的密码全部改掉!关键认知:一旦密码进过 Git(哪怕已删除),就必须当作已泄露处理——立即更换密码,而不是只清理仓库。
八、本章小结
| 要点 | 关键 |
|---|---|
| 目的 | 配置文件不出现明文密码 |
| 用法 | ENC(密文),Spring 自动解密 |
| 算法 | PBEWITHHMACSHA512ANDAES_256 |
| 主密钥 | 环境变量 / KMS,绝不进配置文件 |
| 每次密文不同 | 正常,随机盐 + IV |
| 局限 | 不是完整密钥管理方案 |
| 更优方案 | K8s Secret / 云 IAM / 配置中心 |
| 已泄露 | 必须换密码,不只是清仓库 |
动手练习
练习 1:基础题
在项目中集成 Jasypt,把数据库密码加密,主密钥通过环境变量传入,验证应用能正常启动并连上数据库。
练习 2:思考题
你的团队有 5 个环境(dev/test/uat/pre/prod),每个环境有 8 个敏感配置。用 Jasypt 时,主密钥应该每个环境一个还是共用一个?为什么?
下一章:第 49 章:日志体系 →