含Bouncy Castle依赖的项目打包单Jar适配Oracle JRE问题求助
解决Bouncy Castle签名与Oracle JRE单Jar打包冲突问题
问题背景
项目依赖自研模块,而自研模块依赖带签名的Bouncy Castle(BC)库,打包时陷入两难:
- 若将BC的签名文件(
.SF/.DSA/.RSA)包含在shaded Jar中,JVM会判定签名无效,报错如下:
Error: A JNI error has occurred, please check your installation and try again Exception in thread "main" java.lang.SecurityException: Invalid signature file digest for Manifest main attributes at java.base/sun.security.util.SignatureFileVerifier.processImpl(SignatureFileVerifier.java:340) at java.base/sun.security.util.SignatureFileVerifier.process(SignatureFileVerifier.java:282) at java.base/java.util.jar.JarVerifier.processEntry(JarVerifier.java:320) at java.base/java.util.jar.JarVerifier.update(JarVerifier.java:232) at java.base/java.util.jar.JarFile.initializeVerifier(JarFile.java:760) at java.base/java.util.jar.JarFile.getInputStream(JarFile.java:856) at java.base/jdk.internal.loader.URLClassPath$JarLoader$2.getInputStream(URLClassPath.java:848) at java.base/jdk.internal.loader.Resource.cachedInputStream(Resource.java:77) at java.base/jdk.internal.loader.Resource.getByteBuffer(Resource.java:163) at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:853) at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760) at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681) at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526) at java.base/java.lang.Class.forName0(Native Method) at java.base/java.lang.Class.forName(Class.java:534) at java.base/java.lang.Class.forName(Class.java:513) at java.base/sun.launcher.LauncherHelper.loadMainClass(LauncherHelper.java:797) at java.base/sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:692)
- 若排除签名文件,OpenJDK JRE可正常运行,但Oracle JRE要求JCE安全提供商必须签名,触发报错:
java.lang.SecurityException: JCE cannot authenticate the provider BC at java.base/javax.crypto.JceSecurity.getInstance(JceSecurity.java:150) at java.base/javax.crypto.KeyAgreement.getInstance(KeyAgreement.java:280) at ... (my dependency's classes) Caused by: java.util.jar.JarException: The JCE Provider file:/app/simple-usecase-0.0.6.jar is not signed. at javax.crypto.JarVerifier.verifySingleJar(JarVerifier.java:440) ~[?:?] at javax.crypto.JarVerifier.verifyJars(JarVerifier.java:319) ~[?:?] at javax.crypto.JarVerifier.verify(JarVerifier.java:263) ~[?:?] at javax.crypto.ProviderVerifier.verify(ProviderVerifier.java:130) ~[?:?] at javax.crypto.JceSecurity.verifyProvider(JceSecurity.java:196) ~[?:?] at javax.crypto.JceSecurity$2.apply(JceSecurity.java:222) ~[?:?] at javax.crypto.JceSecurity$2.apply(JceSecurity.java:211) ~[?:?] at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708) ~[?:?] at javax.crypto.JceSecurity.getVerificationResult(JceSecurity.java:211) ~[?:?] at javax.crypto.JceSecurity.getInstance(JceSecurity.java:146) ~[?:?] at javax.crypto.KeyAgreement.getInstance(KeyAgreement.java:280) ~[?:?] at ... (my dependency's classes again)
目标运行环境:
java 21.0.3 2024-04-16 LTS Java(TM) SE Runtime Environment (build 21.0.3+7-LTS-152) Java HotSpot(TM) 64-Bit Server VM (build 21.0.3+7-LTS-152, mixed mode, sharing)
已尝试的方案
- Maven Shade插件排除所有签名文件:配置过滤所有依赖的签名文件,OpenJDK正常但Oracle JRE失败,配置如下:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.5.3</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <minimizeJar>false</minimizeJar> <createDependencyReducedPom>true</createDependencyReducedPom> <dependencyReducedPomLocation> ${java.io.tmpdir}/dependency-reduced-pom.xml </dependencyReducedPomLocation> <relocations> <relocation> <pattern>com.acme.coyote</pattern> <shadedPattern>hidden.coyote</shadedPattern> </relocation> </relocations> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>Main</mainClass> </transformer> </transformers> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> </configuration> </execution> </executions> </plugin>
- Maven Assembly插件打包:使用
jar-with-dependenciesdescriptor,同样OpenJDK正常、Oracle JRE失败。 - 排除BC依赖手动添加类路径:在Shade配置中排除所有BC依赖,运行时用
-cp指定BC Jar,但类加载器无法找到BC类(OpenJDK也失败)。
项目使用OpenJDK 21编译,目标版本21,BC依赖:
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk18on</artifactId> <version>1.78.1</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcutil-jdk18on</artifactId> <version>1.78.1</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk18on</artifactId> <version>1.78.1</version> </dependency>
可行解决方案
方案1:精准过滤签名文件,仅保留BC的签名
修改Shade插件的filter配置,只排除非BC依赖的签名文件,保留BC自身的签名:
<filters> <!-- 排除所有非BC依赖的签名文件 --> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> <includes> <include>org/bouncycastle/**</include> </includes> </filter> <!-- 保留BC依赖的签名文件 --> <filter> <artifact>org.bouncycastle:*</artifact> <includes> <include>META-INF/*.SF</include> <include>META-INF/*.DSA</include> <include>META-INF/*.RSA</include> </includes> </filter> </filters>
这样既不会因为混合签名导致JVM判定无效,又能保留BC的签名满足Oracle JRE的JCE验证要求。
方案2:将BC作为外部依赖打包,通过Manifest指定Class-Path
使用Shade插件将BC Jar打包到最终Jar的lib目录,并在Manifest中添加Class-Path指向这些Jar:
- 配置Shade插件排除BC依赖并生成正确的Manifest:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.5.3</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <minimizeJar>false</minimizeJar> <createDependencyReducedPom>true</createDependencyReducedPom> <relocations> <relocation> <pattern>com.acme.coyote</pattern> <shadedPattern>hidden.coyote</shadedPattern> </relocation> </relocations> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>Main</mainClass> <manifestEntries> <Class-Path>lib/bcprov-jdk18on-1.78.1.jar lib/bcutil-jdk18on-1.78.1.jar lib/bcpkix-jdk18on-1.78.1.jar</Class-Path> </manifestEntries> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/> </transformers> <artifactSet> <excludes> <exclude>org.bouncycastle:*</exclude> </excludes> </artifactSet> </configuration> </execution> </executions> </plugin>
- 添加
maven-dependency-plugin复制BC Jar到lib目录:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>3.6.1</version> <executions> <execution> <id>copy-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/lib</outputDirectory> <includeGroupIds>org.bouncycastle</includeGroupIds> </configuration> </execution> </executions> </plugin>
打包后,将最终Jar和lib目录放在同一目录下运行,Oracle JRE会加载原始签名的BC Jar,满足验证要求。
方案3:对最终Shaded Jar重新签名
若有合法的代码签名证书,可以对排除了第三方签名后的Shaded Jar进行重新签名:
- 先使用Shade插件排除所有签名文件(如之前的配置)
- 使用
jarsigner工具对最终Jar签名:
jarsigner -keystore your-keystore.jks -storepass your-password your-shaded.jar your-alias
签名后的Jar会被Oracle JRE认可为合法的JCE提供商容器。
内容的提问来源于stack exchange,提问作者Felipe Rossi
相关产品推荐
相关产品推荐

