ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring Boot 配置文件加密:Jasypt、SM4、KMS 三方案对比

Spring Boot 配置文件加密:Jasypt、SM4、KMS 三方案对比 上个月帮一个朋友排查线上问题日志里赫然打着一串明文的数据库口令当时我俩的表情都很微妙——那份application-prod.yml已经跟着镜像推到了三个环境谁手里都有一份副本。Springboot 配置文件里的敏感信息加密这件事说大不大说小也真不小它不是那种不做系统就跑不起来的刚需而是那种平时没人管、出事一次就够疼的东西。我前后在四个项目里用过不同的方案从最省事的 Jasypt到自己撸一套 SM4 加解密再到把密钥完全托管给外部系统中间踩的坑加起来能写一篇小作文。这篇内容主要面向已经能把 Springboot 项目跑起来、但对配置安全还没系统梳理过的同学也适合正在做技术选型、纠结到底要不要为了几个密码引入一套加密体系的架构同学。我尽量把三种加密保护方法放在同一把尺子上量讲清楚各自的实现步骤、密钥放哪儿、改动量多大、性能损耗多少以及最关键的——它们各自防得住什么、防不住什么。1. 先把问题定义清楚加密到底在防谁很多人一上来就问用哪个加密库好这个问题其实问早了。加密不是目的挡住某一条具体的泄露路径才是目的。配置文件的敏感信息加密如果没想清楚威胁模型很容易做出看起来很安全、实际上白忙一场的方案——比如把密钥和密文写在同一个文件里这种操作我见过不止一次。1.1 配置文件明文的三类典型泄露路径第一类是版本库泄露。这是最常见也最致命的。开发图省事把生产环境的数据库口令、Redis 密码、第三方支付密钥直接写进application-prod.yml提交上去仓库一公开或者权限一放开等于把钥匙挂在门上。就算仓库是私有的几年积累下来离职员工手里的旧克隆、CI 机器上的缓存副本、本地没清理的工作区都是潜在的出口。第二类是制品与镜像泄露。Springboot 打出来的 fat jar 里BOOT-INF/classes/下面就是打包进去的配置文件。镜像推到私有仓库任何有拉取权限的人unzip一下就能看到明文。哪怕你后来把仓库里的文件删了历史镜像里那份依然躺着。第三类是运行时被动暴露。主机上cat一下配置文件、运维通过跳板机看一眼、监控采集脚本把整个配置目录打包上传这些都属于这一类。还有一类比较隐蔽的某些框架在启动失败时会把加载到的配置项打进异常堆栈如果日志采集做得粗放等于把密码写进了日志系统。注意这三类路径对应的防护手段完全不同。版本库泄露靠不提交明文镜像泄露靠打包前替换运行时暴露靠文件权限 最小可见范围。加密只是其中一环。1.2 加密方案解决的是哪一段把话说明白配置文件加密防的是静态文件被读取不防运行时被攻击。密钥最终要在应用进程里被加载明文口令最终要出现在内存里数据库连接池拿到的还是明文。所以它挡不住内存 dump、挡不住反编译、挡不住一个已经能执行任意代码的攻击者。它真正挡住的是别人拿到了你的 jar 包、拿到了你的配置文件副本、拿到了镜像层但如果他拿不到密钥这些密文就是一堆无意义的字符串。这就是它的全部价值也是它足够的价值——因为现实中绝大多数的配置泄露事故就是文件流出去了而不是攻击者精准 dump 了你的堆内存。所以判断标准很简单密钥和密文不能共存于同一个可泄露边界内。仓库里的密文密钥应该在 CI 的变量里镜像里的密文密钥应该在容器运行时的 Secret 里配置中心里的密文密钥应该在服务端或者独立的下发通道里。任何破坏这条规则的设计加密强度再高都是摆设。1.3 三种方案的选择坐标系我把实际用过的方案归成三类后面会逐个展开维度Jasypt自研 SM4/AES 环境后置处理外部托管配置中心/KMS/Secret接入成本低加依赖加注解中要写加解密类和扩展点高要对接外部系统密钥管理容易做错靠自觉自己掌控责任也自己扛由外部系统承担多环境支持靠不同密钥或不同密文灵活可按 profile 分流天然隔离团队协作密文需共享密钥需分发同左但可做工具化权限体系内解决适用规模中小项目、单体中大型、有安全合规要求微服务、多团队、云原生这张表先放在这里后面几章会用具体数据把它填满。2. 方案一Jasypt二十分钟能跑起来的方案如果你的诉求是今天下班前把仓库里的明文密码干掉Jasypt 基本是唯一理性的选择。它的原理非常朴素提供一个StringEncryptor然后在 Spring 的PropertySource层做一次拦截凡是形如ENC(密文)的值读取的时候自动解密成明文。对业务代码完全透明你该怎么写Value还怎么写。2.1 依赖引入与版本选择的坑Maven 里就一行dependency groupIdcom.github.ulisesbocchio/groupId artifactIdjasypt-spring-boot-starter/artifactId version3.0.5/version /dependency版本这里有个真实的坑。3.0.x是适配 Spring Boot 3.x 的如果你项目还在 Spring Boot 2.x 上混用会碰到自动配置类加载失败或者NoSuchMethodError。反过来Spring Boot 3 项目用2.1.x更是不行因为 Spring Boot 3 把javax.*换成了jakarta.*而 2.x 的启动器还依赖旧包路径。我一般这么处理Spring Boot 2.7 及以下用2.1.2Spring Boot 3.x 用3.0.5。另外注意 JDK 版本Jasypt 默认算法是PBEWITHHMACSHA512ANDAES_256这个算法需要 JDK 8u161 以上才默认支持 AES-256 强度。更早的 JDK 8 版本因为加密策略限制会抛InvalidKeyException那时候要么升级 JDK要么手动装策略文件要么退回PBEWITHHMACSHA512ANDDESEDE。现在还在用 8u161 之前版本的项目应该很少了但如果你维护的是老系统这个点一定要确认。还有一个容易忽略的如果你用的是spring-boot-starter-parent做依赖管理注意别让父 POM 把 Jasypt 的传递依赖降到不兼容的版本必要时显式锁一下版本。2.2 密钥到底放哪里这是整个方案的分水岭配置长这样spring: datasource: password: ENC(3f9a2c8e7b1d4f6a...) jasypt: encryptor: password: ${JASYPT_PASSWORD} algorithm: PBEWITHHMACSHA512ANDAES_256 iv-generator-classname: org.jasypt.iv.RandomIvGenerator注意jasypt.encryptor.password这里我写的是${JASYPT_PASSWORD}也就是从环境变量取。这是关键。如果你直接写password: mySecretKey123那么恭喜你你只是把明文密码换成了明文密钥 密文密码攻击者拿到文件两秒钟就能还原安全增益接近零。密钥的投放方式我按推荐度排个序容器/编排层的 SecretK8s 的 Secret、Docker Compose 的 secrets以环境变量或挂载文件的形式注入。这是目前生产环境最稳妥的做法。启动脚本里 exportexport JASYPT_PASSWORDxxx java -jar app.jar适合传统虚拟机部署。注意脚本本身的权限要收紧到600。-Djasypt.encryptor.passwordxxx能用但不推荐。JVM 参数在ps -ef里是明文可见的同一台机器上的其他用户都能看到。CI/CD 的加密变量在流水线的构建或部署阶段注入注意别把变量回显到构建日志里。提示环境变量也不是绝对安全/proc/pid/environ在同一主机上有权限的前提下是能读到的。它比命令行参数强但比挂载文件 严格文件权限稍弱。生产环境我一般选挂载文件的方式。2.3 生成密文并写回配置文件生成密文有三种办法我按使用频率排命令行方式适合临时用一次java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI \ inputMyRealPassword \ passwordYourMasterKey \ algorithmPBEWITHHMACSHA512ANDAES_256 \ ivGeneratorClassNameorg.jasypt.iv.RandomIvGenerator输出里会给出ENC(...)的完整字符串直接贴进配置文件。代码方式适合批量处理或者集成到运维脚本里public class GenCipher { public static void main(String[] args) { PooledPBEStringEncryptor encryptor new PooledPBEStringEncryptor(); SimpleStringPBEConfig config new SimpleStringPBEConfig(); config.setPassword(System.getenv(JASYPT_PASSWORD)); config.setAlgorithm(PBEWITHHMACSHA512ANDAES_256); config.setKeyObtentionIterations(1000); config.setPoolSize(4); config.setSaltGeneratorClassName(org.jasypt.salt.RandomSaltGenerator); config.setIvGeneratorClassName(org.jasypt.iv.RandomIvGenerator); config.setStringOutputType(base64); encryptor.setConfig(config); System.out.println(ENC( encryptor.encrypt(MyRealPassword) )); } }单元测试里注意测试类加载 Spring 上下文时会尝试解密如果环境变量没设置就会直接启动失败。我的做法是在src/test/resources下放一个application-test.yml里面用测试环境自己的密钥并且把测试环境的配置项换成测试值。别想着在测试代码里硬编码生产密钥那条路走一次就会后悔。2.4 自定义 Encryptor 与几个必须知道的细节Jasypt 允许你自定义StringEncryptor但有个硬性约束Bean 的名称必须叫jasyptStringEncryptor否则自动配置会忽略你的实现转而创建默认的。这是很多人改了半天发现没生效的原因。Configuration public class EncryptorConfig { Bean(jasyptStringEncryptor) public StringEncryptor stringEncryptor() { PooledPBEStringEncryptor encryptor new PooledPBEStringEncryptor(); SimpleStringPBEConfig config new SimpleStringPBEConfig(); config.setPassword(loadKeyFromSecureSource()); config.setAlgorithm(PBEWITHHMACSHA512ANDAES_256); config.setPoolSize(4); encryptor.setConfig(config); return encryptor; } }还有个很多人第一次会慌的现象同一个明文加密两次结果不一样。因为默认启用了随机 Salt 和随机 IV这其实是好事说明没有用固定向量。但你没法用密文是否相同来判断两个配置项的值是不是一样做配置比对脚本的时候要留意。如果你确实需要确定性输出比如做配置 diff可以把 IV 生成器换成ZeroIvGenerator不过我不建议——那会削弱安全性只在特定工具场景下用。最后一个细节poolSize建议设成 4 到 8。默认是 1意味着所有解密请求串行。虽然配置文件里的加密项通常就十几二十个启动阶段影响不大但如果你的场景里有运行时的动态解密需求池子太小会成为瓶颈。3. 方案二自研 SM4/AES 加解密把主动权拿回来Jasypt 用得越久越会碰到它的天花板算法和填充方式是它定的密钥派生逻辑是它定的加密串格式是它定的。有些团队出于国产化合规或者内部密码规范的考虑需要指定 SM4、需要自己控制 IV、需要把加解密逻辑统一到公司的基础库里。这时候就得自己动手了。3.1 为什么要自研以及它的代价自研的收益有三块。一是算法可控可以按内部规范选 SM4-CBC 或者 AES-GCM可以统一 IV 和 Tag 的长度约定。二是格式可控可以设计成ENC{版本号:密文}这种带版本标记的格式将来换算法时能做平滑迁移。三是链路可控加密、解密、密钥加载三个环节都在自己的代码里出问题能定位到行号。代价也很实在你得自己处理密钥加载、自己保证 IV 不重复、自己写扩展点接入 Spring 的配置加载流程还要自己承担写错的风险。我见过自研实现里把 IV 写死成 16 个 0 的也见过用 ECB 模式的这些都在无形中把加密强度打回原形。所以自研的前提是团队里得有人真的懂这块而不是抄一段代码能跑就行。3.2 拦截时机EnvironmentPostProcessor 怎么挂上去要在 Spring 读取配置文件的过程中把密文替换掉最合适的扩展点是EnvironmentPostProcessor。它在ApplicationEnvironmentPreparedEvent阶段被调用此时配置文件已经加载进Environment的PropertySource里但 Bean 还没开始创建任何Value和ConfigurationProperties拿到的都将是解密后的值。public class SensitivePropertyDecryptor implements EnvironmentPostProcessor, Ordered { Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { for (PropertySource? source : environment.getPropertySources()) { if (source instanceof EnumerablePropertySource) { decryptSource((EnumerablePropertySource?) source, environment); } } } SuppressWarnings(unchecked) private void decryptSource(EnumerablePropertySource? source, ConfigurableEnvironment environment) { MapString, Object decrypted new HashMap(); for (String name : source.getPropertyNames()) { Object value source.getProperty(name); if (value instanceof String ((String) value).startsWith(sm4()) { decrypted.put(name, Sm4CryptoUtil.decrypt(unwrap((String) value))); } } if (!decrypted.isEmpty()) { environment.getPropertySources() .addFirst(new MapPropertySource(source.getName() -decrypted, decrypted)); } } Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } }这里有两个必须注意的点。第一执行顺序。Spring Boot 2.4 之后配置文件的加载改由ConfigDataEnvironmentPostProcessor负责它的 order 是Ordered.HIGHEST_PRECEDENCE 10。如果你的处理器执行得比它早PropertySource里根本还没有配置文件的内容自然什么都解密不了。所以要么把 order 设得足够大比如LOWEST_PRECEDENCE要么显式设成比它大一点的值比如Ordered.HIGHEST_PRECEDENCE 20。第二注册方式变了。Spring Boot 2.7 开始EnvironmentPostProcessor的注册从META-INF/spring.factories迁移到了META-INF/spring/org.springframework.boot.env.EnvironmentPostProcessor.imports。文件内容就是实现类的全限定名一行一个。如果你在 Spring Boot 3 项目里还往spring.factories里写是不会生效的而且不会有任何报错——就是静默不工作特别容易被坑。# META-INF/spring/org.springframework.boot.env.EnvironmentPostProcessor.imports com.example.config.SensitivePropertyDecryptor还有一个细节遍历PropertySource时不要在原对象上改。EnumerablePropertySource的getProperty是只读的你没法回写。正确做法是收集所有解密结果构造一个新的MapPropertySource用addFirst插到最前面让它的优先级高于原始配置源。这样原始密文还在但读取时拿到的是解密值。3.3 SM4-CBC 与 AES-GCM 的实现细节国产化场景下 SM4 是常见选择密钥固定 128 位16 字节分组也是 128 位。用 Hutool 的话大概是这样public final class Sm4CryptoUtil { private static final byte[] KEY HexUtil.decodeHex( System.getenv(CONFIG_SM4_KEY)); private static final byte[] IV HexUtil.decodeHex( System.getenv(CONFIG_SM4_IV)); public static String encrypt(String plain) { SM4 sm4 SmUtil.sm4(KEY); sm4.setMode(Mode.CBC); sm4.setPadding(Padding.PKCS5Padding); sm4.setIv(IV); return sm4.encryptBase64(plain); } public static String decrypt(String cipher) { SM4 sm4 SmUtil.sm4(KEY); sm4.setMode(Mode.CBC); sm4.setPadding(Padding.PKCS5Padding); sm4.setIv(IV); return sm4.decryptStr(cipher); } }这里我把 IV 也放在环境变量里。严格来说 CBC 模式下 IV 只需要满足不可预测不需要保密但很多内部安全规范会要求一起保护跟着来吧。更推荐的其实是 AEAD 模式。SM4-GCM 或者 AES-GCM 自带完整性校验密文被篡改会直接抛异常能防住 CBC 模式下的填充预言类问题。AES-GCM 的 Java 实现大致是public static String aesGcmEncrypt(String plain, byte[] key) throws Exception { byte[] iv new byte[12]; // GCM 推荐 12 字节 IV SecureRandom.getInstanceStrong().nextBytes(iv); Cipher cipher Cipher.getInstance(AES/GCM/NoPadding); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, AES), new GCMParameterSpec(128, iv)); // Tag 长度 128 位 byte[] out cipher.doFinal(plain.getBytes(StandardCharsets.UTF_8)); byte[] result new byte[iv.length out.length]; // IV 前置拼接 System.arraycopy(iv, 0, result, 0, iv.length); System.arraycopy(out, 0, result, iv.length, out.length); return Base64.getEncoder().encodeToString(result); }关键点是GCM 的 IV 绝对不能重复同一个密钥下重复 IV 会直接摧毁安全性。所以不要用固定 IV也不要用时间戳当 IV老老实实用SecureRandom每次加密随机生成然后把 IV 和密文拼在一起存。解密时前 12 字节切出来当 IV剩下的是密文加 Tag。注意不管选哪种模式都不要用 ECB。ECB 对相同明文块产生相同密文块配置文件里如果有重复的密码片段会直接暴露规律。这是最基础也最常被忽视的一条。3.4 和配置中心、多环境 Profile 的配合自研方案在多环境下比 Jasypt 灵活得多因为密钥来源完全由你控制。典型做法是密钥前缀带环境标识加载时按spring.profiles.active选择对应的密钥。String env environment.getProperty(spring.profiles.active, dev); String keyName CONFIG_KEY_ env.toUpperCase(); String key System.getenv(keyName);这样一套代码在开发、预发、生产用三把不同的密钥密文也各不相同。有人在application.yml里用 Profile 分组的方式写多套密文我不太推荐——那等于把三套密文和对应的密钥使用规则全放在一个文件里缩小了攻击成本。配合配置中心时思路要变一下让配置中心存密文应用启动时解密。也就是配置中心下发的是sm4(...)格式的串本地做最后一道解密。这样配置中心的运维人员看到的也是密文适合那种配置平台权限比较宽、但业务密钥必须收口的组织。4. 方案三把密钥托管出去让应用不碰密钥前两种方案的共同点是密钥最终还是要落到应用进程里。方案三的思路是换个方向——让应用根本不持有长期密钥或者让密钥的获取过程本身就是一次受控的、可审计的交互。4.1 配置中心的服务端加密主流配置中心大多提供了加密能力思路是把加解密挪到服务端。运维在控制台里录入明文服务端用主密钥加密后存储客户端拉取时服务端解密或者下发密文由客户端解密取决于具体实现。应用侧的改动通常很小有的只需要加一个依赖配置项本身完全不用动。这个方案的真正价值在于权限收口谁能在控制台看到明文、谁只能看到密文、谁只能读不能改都能在平台层面控制。配置文件再也不会散落在各个开发者的笔记本上因为本地开发压根不读生产配置。不过有两个前提要确认清楚。一是主密钥的保护级别如果配置中心的主密钥放在一个权限很松的配置文件里那就是把风险从业务系统转移到了中间件总量没降。二是网络与认证链路客户端访问配置中心必须走内网且带认证否则拉配置这个动作本身就变成了新的泄露口。4.2 容器 Secret 与 configtree 导入容器环境下的做法比较成熟。K8s 的 Secret 本质上是把敏感数据单独存一份通过环境变量或挂载卷注入容器。挂载卷的方式更安全一些因为不会出现在进程环境里。apiVersion: v1 kind: Pod spec: containers: - name: app image: registry.example.com/demo:1.0.0 env: - name: SPRING_PROFILES_ACTIVE value: prod volumeMounts: - name: app-secrets mountPath: /etc/app/secrets readOnly: true volumes: - name: app-secrets secret: secretName: app-secret然后在application.yml里加一行spring: config: import: optional:configtree:/etc/app/secrets/configtree这个导入方式特别适合 secret 场景它会把目录下的每个文件当成一个配置项文件名就是 key文件内容就是 value。比如/etc/app/secrets/db.password这个文件的内容就会变成db.password这个属性。这样你的配置文件里连密文都不用写只有一行导入语句真正的值全在运行时注入。这种做法的好处是配置文件可以完全公开扔到哪都不怕。Docker Compose 也有对应的 secrets 机制services: app: image: demo:1.0.0 secrets: - db_password secrets: db_password: file: ./secrets/db_password.txt默认挂载到/run/secrets/下同样可以用configtree导入。4.3 KMS 动态解密与凭证轮转再往上走一层是接入密钥管理服务。应用启动时用实例身份比如容器工作负载身份向 KMS 请求解密一段数据密钥用这个数据密钥解出配置密文数据密钥用完即弃不落盘。这样应用进程里从头到尾不持有长期密钥泄露面被压到极小。代价是启动依赖外部服务KMS 不可用时应用起不来需要设计降级或者缓存策略。我的做法是在本地缓存一份短期有效的数据密钥比如挂载在 tmpfs 里配 5 分钟过期KMS 短暂抖动时还能撑一会儿但绝不做永久缓存。密钥轮转是这套体系最容易被忽略的部分。很多团队上线时认真配了加密之后三年没换过密钥人员流动了几轮密钥等于半公开。我在项目里一般遵循这个节奏主密钥至少每季度轮转一次密文在轮转时批量重加密人员离职触发一次即时轮转。轮转的过程要有脚本支撑靠手工改配置迟早出事。5. 三种方案横向对比与实测记录说了这么多实现最终还是要落到选哪个。我把三个方案在同一个项目上做过对比测试环境和数据如下。5.1 关键维度对比表维度Jasypt自研 SM4/AES外部托管首次接入工作量0.5 人天3 到 5 人天5 到 10 人天加密算法可选范围受限于内置算法集完全自主取决于平台能力密文可否被篡改检测默认无完整性校验选 GCM 可检测一般有密钥与密文分离难度依赖团队规范依赖自身设计平台强制分离对业务代码侵入无无扩展点方式无启动耗时影响低低到中中含网络请求离线可启动是是否需缓存或降级密钥轮转成本需重新加密所有配置需重新加密所有配置平台支持较低5.2 启动耗时与操作成本实测测试环境是本地开发机8 核 16GJDK 17Spring Boot 3.2配置文件中包含 20 个加密项每个密文长度在 60 到 90 字符之间连续跑 10 次取平均。方案单次解密平均耗时20 项总解密耗时应用启动总耗时变化不加密基线--4.32 秒JasyptPBEWITHHMACSHA512ANDAES_256约 5.8 ms约 116 ms4.45 秒自研 SM4-CBC约 0.4 ms约 8 ms4.33 秒自研 AES-GCM约 0.3 ms约 6 ms4.33 秒KMS 动态获取数据密钥约 180 ms含网络往返约 180 ms4.62 秒几个结论挺明显的。Jasypt 的慢主要来自它的密钥派生设计每次解密都要做 1000 轮迭代的 PBE 运算keyObtentionIterations默认值这是为了对抗暴力破解安全性上是有意义的代价就是慢了一个数量级。20 个配置项 116 毫秒对启动时间来说完全可以接受但如果你的配置项有几百个或者有运行时动态解密的场景这个开销就要认真评估了。自研的对称加解密快得几乎无感因为 SM4 和 AES 都是硬件友好型算法加上现代 CPU 的 AES-NI 指令集单次加解密都在微秒级。8 毫秒和 116 毫秒的差距在配置项多的时候会放大到秒级。KMS 的开销全在网络往返上一次请求 180 毫秒左右但只要不是每读一个配置就请求一次整体影响也可控。关键是做好一次获取、多次复用。提示上面这些数据是本地实测和生产环境的绝对数值会有差异但相对关系是稳定的。评估的时候看比例不要照抄绝对值。5.3 什么项目该选哪个我的判断标准大概是这样中小型项目、单体应用、团队规模 5 人以内、没有专职安全同学直接用 Jasypt。它的接入成本最低收益也最直接只要把密钥投放这件事管住实际防护效果足够。别为了技术先进去自研自研写错了还不如不加密。中大型项目、有内部密码算法规范、需要指定 SM4 或者需要控制密文格式走自研路线。但要先把三件事定下来算法和模式推荐 AEAD、密钥的存放和轮转机制、密文的格式约定建议带版本前缀方便将来迁移。这三件事没有明确答案之前不要动笔写代码。微服务架构、多团队共享配置、有云原生基础设施用外部托管。这类场景下配置项数量大、环境多、人员流动快靠人工规范根本管不住必须用平台机制强制约束。前提是你得接受应用启动依赖外部系统这个事实并且做好降级预案。一个折中做法是混合配置文件里的密文用自研方案解长期密钥本身放在容器 Secret 或者 KMS 里。这样既有自研的灵活度和性能又把最关键的根密钥托管出去两层防护。6. 踩坑实录与问题速查下面这些是我实际碰到过的问题有些花了不少时间才定位到整理成速查表方便对照。6.1 常见问题速查表现象大概率原因处理方式启动报DecryptionException密钥不对或密文被换行/空格污染检查密钥来源确认密文没被编辑器自动折行Jasypt 自定义 Encryptor 不生效Bean 名称不是jasyptStringEncryptor改 Bean 名称或显式指定bean属性Spring Boot 3 中扩展点静默失效还在用spring.factories注册改用META-INF/spring/xxx.imports解密拿到的是ENC(...)原文处理器执行顺序早于配置加载把 order 调到HIGHEST_PRECEDENCE 20之后单元测试启动失败测试环境没有密钥用测试专用密钥和测试配置文件同一明文加密结果每次都不同启用了随机 Salt 和 IV正常现象不是 Bug中文口令解密后乱码编码不一致加密端用了 UTF-8解密端用了平台默认两端统一显式指定StandardCharsets.UTF_8容器里读不到挂载的密钥文件挂载路径或文件权限不对检查mountPath和文件属主Spring 进程用户要能读换了新密钥后老密文解不开没有做版本标记和兼容逻辑密文格式带版本号保留旧密钥一段时间6.2 几条我自己的实操心得第一条加密这件事最怕半途而废。我见过项目里改了一半application-prod.yml加密了application-dev.yml还是明文结果生产密钥和开发密钥是同一把。这种情况下攻击者从开发配置入手一样能拿到生产库的入口。要么全环境统一做要么至少保证每个环境用独立密钥。第二条配置文件里的注释和提交历史也是泄露源。有人加密了密码但注释里写着原密码是 xxx2024-03 更换有人加密了之后在提交信息里写把 xxx 密码加密了。这些细节在代码审查里经常被放过但泄露效果和明文一样直接。第三条写一个内部的小工具。团队里每个人手工跑命令行加密格式迟早会不统一。我一般会在项目里加一个maven插件或者一个独立的 CLI 模块输入明文输出ENC(...)自动带上正确的算法参数。工具本身也顺手解决了新同学不知道该怎么加密的问题。类似地IDE 里可以配一个外部工具选中一段文本一键转换用起来很顺手。第四条加密配置项的清单要维护。哪些 key 属于敏感信息、哪些不需要加密最好有一份明确的列表放在项目文档里持续维护。我见过把整个application.yml无差别加密的做法结果连日志级别这种非敏感项也进了密文维护成本一下子上去而且完全没必要。第五条别忘了配置文件的文件权限。即使做了加密chmod 600和正确的属主设置依然要做。这是一层几乎零成本的防护很多团队因为反正已经加密了就跳过了其实主机被入侵时它是最先起作用的。第六条定期做一次泄露演练。把打包好的 jar 解压把镜像层扒开把仓库历史翻一遍看看能不能直接搜到密码。我自己做过两次第二次还真的在历史提交里翻出了一条两年前的明文。这类演练不需要什么工具grep -r加上点耐心就够了但效果比读十篇安全规范都实在。第七条密钥轮转要当成常规运维动作而不是应急措施。我现在的做法是在运维日历里固定一个季度提醒到点就走一遍轮转流程生成新密钥、批量重加密、双密钥并行一段时间、下线旧密钥。第一次做会有点手忙脚乱做完两轮之后就顺了而且流程本身会暴露出很多平时看不见的设计问题。最后分享一个小技巧如果你不确定某个配置项到底算不算敏感就问自己一句——这条信息出现在公开的博客或者论坛里我会不会不舒服会那就加密。这个土办法判断准确率相当高比翻安全规范快多了。
返回列表