前言

呵呵 这是某技术交流群里面暴露出的一个问题 

大致存在的情况是 oracle jdk19 执行 spring-boot 打包使用 bouncycastle.bcprov 的类库, 执行报错, 报错如下 

但是奇怪的是 直接执行该 主类, 是可以正常执行的 

另外 使用 openjdk19, 也是可以正常执行的 

master:~ jerry$ /Library/Java/JavaVirtualMachines/jdk-19.jdk/Contents/Home/bin/java -jar /Users/jerry/IdeaProjects/HelloSpringBoot/target/HelloSpringBoot-0.0.1.jar
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:119)
	at java.base/java.lang.reflect.Method.invoke(Method.java:578)
	at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:87)
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:50)
	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
Caused by: java.security.NoSuchProviderException: JCE cannot authenticate the provider BC
	at java.base/javax.crypto.JceSecurity.getInstance(JceSecurity.java:131)
	at java.base/javax.crypto.KeyGenerator.getInstance(KeyGenerator.java:286)
	at com.hx.boot.Test23BcprovCodecNotFound.digest(Test23BcprovCodecNotFound.java:28)
	at com.hx.boot.Test23BcprovCodecNotFound.main(Test23BcprovCodecNotFound.java:24)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
	... 5 more
Caused by: java.util.jar.JarException: The JCE Provider jar:file:/Users/jerry/IdeaProjects/HelloSpringBoot/target/HelloSpringBoot-0.0.1.jar!/BOOT-INF/lib/bcprov-jdk18on-1.71.jar!/ is not signed.
	at java.base/javax.crypto.JarVerifier.verifySingleJar(JarVerifier.java:464)
	at java.base/javax.crypto.JarVerifier.verifyJars(JarVerifier.java:317)
	at java.base/javax.crypto.JarVerifier.verify(JarVerifier.java:260)
	at java.base/javax.crypto.ProviderVerifier.verify(ProviderVerifier.java:130)
	at java.base/javax.crypto.JceSecurity.verifyProvider(JceSecurity.java:190)
	at java.base/javax.crypto.JceSecurity.getVerificationResult(JceSecurity.java:218)
	at java.base/javax.crypto.JceSecurity.getInstance(JceSecurity.java:128)
	... 9 more

 

该问题交流的上下文如下, 日期是 2022-10-11 

北京-哇咔咔  10:38:44
我有一个问题,jdk17及以上版本 上运行springboot胖jar无法执行bouncycastle的加密包。但是在ide里用单元测试和main方法可以执行。
jdk9--jdk16没用测试过。
谁遇到过这个问题?
成都-蓝风  12:38:28
@北京-哇咔咔 无法执行是报什么错?
北京-哇咔咔  16:01:43
@成都-蓝风 我正在重新做测试 ,测试完了再说结果。我好像发现问题所在了
北京-哇咔咔  17:26:19
@成都-蓝风 出加密/签名算法找不到的错误,把bouncycastle升级到jdk18on 1.72 也是出一样的错。
我再把jdk换成jdk18试试
北京-哇咔咔  18:37:53
这个破案了。
用oracle jdk19不行,换成openjdk19就行了
成都-蓝风  19:09:38
@北京-哇咔咔 哦, 之前碰到过一个 bouncycastle 类层级循环依赖的错误, 但是 你这里似乎是不一样
org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖
成都-蓝风  19:09:47
@北京-哇咔咔 你又测试代码么,
北京-哇咔咔  19:10:36
spring-boot:2.4.13
bcprov-jdk18on:1.71
public class Test {

    public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());

        System.out.println(Arrays.toString(digest("hello")));
    }

    public static byte[] digest(String data) throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacMD5", "BC");
        SecretKey secretKey = keyGenerator.generateKey();
        Mac mac = Mac.getInstance(secretKey.getAlgorithm(), "BC");
        mac.init(secretKey);
        return mac.doFinal(data.getBytes());
    }

}
北京-哇咔咔  19:11:06
编译成 fatjar 执行
成都-蓝风  19:12:35
@北京-哇咔咔 好的
北京-哇咔咔  19:13:07
springboot 和 bouncycastle 都换成最新的不影响结果
北京-哇咔咔  19:47:19
openjdk19 可以,oraclejdk19 不行。oraclejdk17 也不行,oraclejdk8 以后 17 以前的没试过。
成都-蓝风  19:58:45
@北京-哇咔咔 我看我这边 mac, windows 上面 都没得问题 ..
成都-蓝风  19:58:49
都能 正常跑
北京-哇咔咔  19:59:04
oraclejdk?
北京-哇咔咔  19:59:12
oraclejdk19?
成都-蓝风  19:59:50
oracle jdk 19, 但是 只添加了 依赖 bcprov-jdk18on:1.71
西安-鈺术  20:00:41
我们这边也是好多未知异常都是oraclejdk的  换成openjdk一切就正常他
北京-哇咔咔  20:01:00
用 java -jar 运行的?
成都-蓝风  20:01:26
哦 没有打 jar 包呀
成都-蓝风  20:01:58
@北京-哇咔咔 你那里 直接 idea 里面跑能复现么
北京-哇咔咔  20:02:22
不能
北京-哇咔咔  20:06:51
不同的实现行为表现居然不一样
@北京-哇咔咔 你这里面是 main, java -jar 咋个启动的呀
福州-人生海海  20:10:07
MF文件指定下启动类不就行了吗。。
成都-蓝风  20:12:20
master:target jerry$ java -jar HelloSpringBoot-0.0.1.jar
[-69, -83, 79, -90, -109, 114, -87, -9, -40, -47, -67, -116, -75, -63, 40, 24]
@北京-哇咔咔
… mac 上面 也没错
成都-蓝风  20:17:14
[图片]
win 上面有一个这个错误
北京-哇咔咔  21:36:54
就是这个错
深圳-准备求职  21:53:13
oracle jdk要自己将provider扔进某个文件夹吧
北京-哇咔咔  21:54:13
8 以前可以
北京-哇咔咔  21:54:21
9 开始就不行了
北京-哇咔咔  22:07:30
不对,8 开始就不行了
中山-划船  23:03:59
我这里jdk17也没见有报这个错
北京-哇咔咔  06:39:13
用的第三方加密包,怎么会跟 jdk 冲突?@中山-划船
北京-哇咔咔  06:39:49
@中山-划船 你先确定是不是 openjdk
北京-哇咔咔  08:03:23
@中山-划船 包名都不一样怎么冲突?
我直接在 idea 运行是可以的。编译成 springboot fatjar 就不行。你是用 fatjar 运行的?
北京-哇咔咔  09:54:13
@福州-人生海海 我也是这么猜的
武汉-大象  09:55:53
@北京-哇咔咔 坨神,你这么先进的吗?都开始升级到19了
北京-哇咔咔  10:18:05
从jdk8直升jdk19
北京-哇咔咔  10:19:22
给maven增加--resource 19 --enable-preview编译参数,出编译错误。去掉这个编译参数也出编译错误
北京-哇咔咔  10:27:19
我电脑qq怎么收不到消息了[表情]
北京-哇咔咔  10:27:26
手机还能收到
北京-哇咔咔  10:32:45
又试了一下,--release 19这个参数maven还不支持,--enable-preview可以用。现在启动试试
北京-哇咔咔  10:32:52
goroutine也可以几百万。
北京-哇咔咔  21:11:26
@中山-划船 我说的是最高。草
北京-哇咔咔  21:14:09
1.8 当然可以,但是不管是几,只要编译成 springboot fatjar,就不能在 jdk19 上 执行第三方加密包。我在 jdk17 上也执行了,一样不行。
北京-哇咔咔  21:16:43
编译器插件支持 在这个位置填 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8     5 6 7 8 9 10 11 12 13 1415
成都-蓝风  22:13:19
1. fatjar
[图片]
成都-蓝风  22:13:27
2. 普通启动
[图片]
成都-蓝风  22:14:23
openjdk 中能够执行成功, 是因为 两个的 ProviderVerifier.verify 的实现有很大的差别
而 oraclejdk19 会走上面的校验流程
@北京-哇咔咔
北京-哇咔咔  22:15:16
[表情]
成都-蓝风  22:16:21
再具体 就涉及到 zip 解码什么内容了, 就有点麻烦了
北京-哇咔咔  22:16:54
但是为什么有不一样的实现?
成都-蓝风  22:23:16
R大以前有文章
https://www.iteye.com/blog/rednaxelafx-1549577

 

 

测试用例

测试用例如下, 主要是试用了一下 bouncycastle.bcprov 的类库

/**
 * Test23BcprovCodecNotFound
 *
 * @author Jerry.X.He <970655147@qq.com>
 * @version 1.0
 * @date 2022-10-11 19:32
 */
public class Test23BcprovCodecNotFound {

    // Test23BcprovCodecNotFound
    public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());

        System.out.println(Arrays.toString(digest("hello")));
    }

    public static byte[] digest(String data) throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacMD5", "BC");
        SecretKey secretKey = keyGenerator.generateKey();
        Mac mac = Mac.getInstance(secretKey.getAlgorithm(), "BC");
        mac.init(secretKey);
        return mac.doFinal(data.getBytes());
    }

}

 

 

使用 jdk19, 执行 spring-boot 打的 fatjar 

master:~ jerry$ /Library/Java/JavaVirtualMachines/jdk-19.jdk/Contents/Home/bin/java -jar /Users/jerry/IdeaProjects/HelloSpringBoot/target/HelloSpringBoot-0.0.1.jar
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:119)
	at java.base/java.lang.reflect.Method.invoke(Method.java:578)
	at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:87)
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:50)
	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
Caused by: java.security.NoSuchProviderException: JCE cannot authenticate the provider BC
	at java.base/javax.crypto.JceSecurity.getInstance(JceSecurity.java:131)
	at java.base/javax.crypto.KeyGenerator.getInstance(KeyGenerator.java:286)
	at com.hx.boot.Test23BcprovCodecNotFound.digest(Test23BcprovCodecNotFound.java:28)
	at com.hx.boot.Test23BcprovCodecNotFound.main(Test23BcprovCodecNotFound.java:24)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
	... 5 more
Caused by: java.util.jar.JarException: The JCE Provider jar:file:/Users/jerry/IdeaProjects/HelloSpringBoot/target/HelloSpringBoot-0.0.1.jar!/BOOT-INF/lib/bcprov-jdk18on-1.71.jar!/ is not signed.
	at java.base/javax.crypto.JarVerifier.verifySingleJar(JarVerifier.java:464)
	at java.base/javax.crypto.JarVerifier.verifyJars(JarVerifier.java:317)
	at java.base/javax.crypto.JarVerifier.verify(JarVerifier.java:260)
	at java.base/javax.crypto.ProviderVerifier.verify(ProviderVerifier.java:130)
	at java.base/javax.crypto.JceSecurity.verifyProvider(JceSecurity.java:190)
	at java.base/javax.crypto.JceSecurity.getVerificationResult(JceSecurity.java:218)
	at java.base/javax.crypto.JceSecurity.getInstance(JceSecurity.java:128)
	... 9 more
master:~ jerry$

 

使用 jdk19 直接执行 class 

master:~ jerry$ /Library/Java/JavaVirtualMachines/jdk-19.jdk/Contents/Home/bin/java -cp "/Users/jerry/.m2/repository/org/bouncycastle/bcprov-jdk18on/1.71/bcprov-jdk18on-1.71.jar:/Users/jerry/IdeaProjects/HelloSpringBoot/target/classes" com.hx.boot.Test23BcprovCodecNotFound
[103, -96, -97, -113, -6, 106, -18, -39, 100, -112, 13, -3, -26, 9, 48, 57]
master:~ jerry$

 

使用 openjdk19 执行 spring-boot 打的 fatjar 

master:~ jerry$ /Library/Java/JavaVirtualMachines/openjdk-19.jdk/Contents/Home/bin/java -jar /Users/jerry/IdeaProjects/HelloSpringBoot/target/HelloSpringBoot-0.0.1.jar
[-19, 4, 59, 65, -17, -20, -51, -66, 16, 24, -59, 52, 77, -13, 77, -68]

 

使用 openjdk19 直接执行 class 

master:~ jerry$ /Library/Java/JavaVirtualMachines/openjdk-19.jdk/Contents/Home/bin/java -cp "/Users/jerry/.m2/repository/org/bouncycastle/bcprov-jdk18on/1.71/bcprov-jdk18on-1.71.jar:/Users/jerry/IdeaProjects/HelloSpringBoot/target/classes" com.hx.boot.Test23BcprovCodecNotFound
[-35, -93, 11, -98, -53, -89, 22, -2, -121, -34, 72, 73, -22, 73, -73, 126]
master:~ jerry$

 

 

问题的调试

核心的问题主要是来自于这里, 获取  "/Users/jerry/IdeaProjects/HelloSpringBoot/target/HelloSpringBoot-0.0.1.jar!/BOOT-INF/lib/bcprov-jdk18on-1.71.jar" 的清单文件, 没有拿到 

104 oracle jdk-19 + spring-boot 打的 fatjar 不能使用 bouncycastle.bcprov_java

 

如果是直接 oraclejdk19 执行 class, 这里从 "/Users/jerry/.m2/repository/org/bouncycastle/bcprov-jdk18on/1.71/bcprov-jdk18on-1.71.jar" 是可以拿到清单文件的 

104 oracle jdk-19 + spring-boot 打的 fatjar 不能使用 bouncycastle.bcprov_jdk_02

 

openjdk19 中不会报错, 是因为 ProviderVerifier.verify 的实现 和 oraclejdk19 还是有较大的差异 

JarVerifier 中的实现也还是有较大的差异, 实现的差异 导致了这些细节的差异 

104 oracle jdk-19 + spring-boot 打的 fatjar 不能使用 bouncycastle.bcprov_spring_03

 

 

是什么导致了 fatjar 获取不到清单文件, 执行 class 可以获取到清单文件 ? 

看一下 ZipFile.getManifestName 中的具体实现, 可以看到的是 res.zsrc.signatureMetaNames 

res.zsrc.signatureMetaNames 没有拿到的主要原因是 在 jar 的元数据头部, 没有 签名相关元数据 

104 oracle jdk-19 + spring-boot 打的 fatjar 不能使用 bouncycastle.bcprov_java_04

 

然后 为什么 spring-boot 打出来的 fatjar 没有 签名相关元数据 以及 签名相关元数据 到底是什么, 这个东西 就不深究了 

但是 这个间接的导致了这里的问题