【异常解决】JDK21升级中SecurityException: JCE无法验证BC提供者的深度解析

张开发
2026/4/15 17:44:31 15 分钟阅读

分享文章

【异常解决】JDK21升级中SecurityException: JCE无法验证BC提供者的深度解析
1. 遇到SecurityException时发生了什么最近在把项目升级到JDK21的时候突然遇到了一个让人头疼的异常SecurityException: JCE cannot authenticate the provider BC。这个错误直接导致我们的加密功能全部瘫痪整个系统都无法正常启动。刚开始看到这个报错的时候说实话有点懵因为之前用JDK8的时候一切正常怎么升级到JDK21就出问题了仔细看错误日志发现关键信息是jar:file:/app//service.jar!/BOOT-INF/lib/bcprov-jdk14-138.jar!/ has unsigned entries。这提示我们项目中使用的Bouncy Castle简称BC加密库的jar包没有经过数字签名。在JDK21中JCEJava Cryptography Extension对加密提供者的验证变得更加严格要求所有加密提供者必须经过签名认证才能使用。2. 为什么JDK21会报这个错2.1 JCE安全机制的升级JDK21对安全机制做了不少改进特别是在加密相关功能上。JCE现在会严格检查所有加密提供者的签名确保它们来自可信来源。这个改动其实是为了提高系统的安全性防止恶意代码伪装成加密提供者混入系统。在我们的案例中项目里混入了老版本的bcprov-jdk14-138.jar这个jar包恰好没有经过签名。在JDK8时代JCE对这方面检查不严格所以能正常运行。但到了JDK21这个无证驾驶的加密提供者就被拦下来了。2.2 依赖冲突的常见表现这类问题通常有几种典型表现应用启动时直接抛出SecurityException加密相关功能突然失效日志中会明确提示哪个jar包有问题错误信息中通常包含unsigned entries这样的关键词如果你在升级JDK后遇到类似情况很大概率也是因为项目中混入了不兼容的老版本加密库。3. 如何定位问题依赖3.1 使用Maven依赖树分析要解决这个问题首先得找到问题jar包是怎么混进来的。Maven提供了一个很实用的命令mvn dependency:tree这个命令会打印出完整的依赖树你可以搜索bcprov或者bouncycastle关键字找到所有相关的依赖项。在我们的案例中发现是通过hutool-all间接引入了老版本的bcprov-jdk14。3.2 检查jar包签名信息你也可以直接检查jar包的签名状态jarsigner -verify bcprov-jdk14-138.jar如果看到jar is unsigned的提示那就确认是这个jar包导致的问题了。4. 完整解决方案步骤4.1 排除冲突依赖首先要在pom.xml中排除有问题的老版本依赖。以我们的项目为例需要这样修改dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId exclusions exclusion artifactIdbcprov-jdk14/artifactId groupIdbouncycastle/groupId /exclusion exclusion artifactIdbcprov-jdk14/artifactId groupIdorg.bouncycastle/groupId /exclusion /exclusions /dependency注意要检查所有可能引入Bouncy Castle的依赖确保把所有老版本都排除干净。4.2 添加兼容的新版本依赖排除老版本后需要添加JDK21兼容的新版本dependency groupIdorg.bouncycastle/groupId artifactIdbcprov-jdk18on/artifactId version1.77/version /dependency dependency groupIdorg.bouncycastle/groupId artifactIdbcpkix-jdk18on/artifactId version1.77/version /dependency这里我推荐使用jdk18on版本因为它对新的JDK版本支持最好。虽然名字里有18但实际上它完全兼容JDK21。4.3 清理和重建项目完成上述修改后一定要执行完整的清理和重建mvn clean install有时候IDE会缓存旧的依赖所以最好在命令行执行这个命令确保所有改动都生效。5. 验证解决方案是否有效5.1 检查运行时依赖启动应用后可以通过以下代码验证当前加载的Bouncy Castle版本Provider bcProvider Security.getProvider(BC); System.out.println(BouncyCastle版本: bcProvider.getVersion());正确的输出应该是你新添加的版本号如1.77而不是之前有问题的老版本。5.2 测试加密功能最后别忘了实际测试加密功能是否恢复正常。可以写个简单的测试用例Test public void testEncryption() throws Exception { KeyPairGenerator keyGen KeyPairGenerator.getInstance(RSA, BC); keyGen.initialize(2048); KeyPair keyPair keyGen.generateKeyPair(); // 如果没有抛出异常说明一切正常 }6. 可能遇到的坑和注意事项6.1 多模块项目的依赖管理如果你的项目有多个模块要特别注意依赖传递问题。有时候一个模块排除了老版本但另一个模块又引入了。这种情况下建议在父pom中统一管理Bouncy Castle的版本。6.2 其他依赖的兼容性问题除了Bouncy Castle本身还要注意其他依赖是否要求特定版本的BC。比如某些安全框架可能会强制指定BC版本。遇到这种情况可能需要升级这些框架到最新版本。6.3 部署环境的检查即使本地测试通过了部署到服务器上还是可能出问题。特别是如果你使用Docker或者CI/CD流水线要确保构建环境没有缓存旧的依赖。一个实用的技巧是在Dockerfile中加入清理命令RUN mvn dependency:purge-local-repository7. 为什么选择jdk18on版本在解决这个问题的过程中我发现Bouncy Castle有多个版本分支jdk14: 老版本已停止维护jdk15on: 较新的版本jdk18on: 最新的版本分支虽然jdk15on也能工作但jdk18on有更多的新特性和性能优化。特别是对于使用JDK17及以上版本的项目jdk18on是最佳选择。它经过了更严格的安全审计也修复了早期版本中的一些漏洞。8. 更深层次的安全考量这个问题表面上看是个技术兼容性问题但实际上涉及重要的安全原则。JDK21强制要求加密提供者必须签名这个改变非常合理。在真实的生产环境中使用未签名的加密组件存在严重的安全风险无法验证代码来源可能被植入恶意代码无法保证代码没有被篡改过难以进行安全审计和漏洞追踪所以虽然这个改动给我们带来了些麻烦但从长远看是值得的。作为开发者我们应该养成只使用经过验证的加密库的好习惯。

更多文章