4月9

从 JDK 8 到 JDK 21 的“握手”血泪史

| |
17:02编程杂谈  From: 本站原创
随着 Java 生态的演进,我们将核心项目从 JDK 8 迁移至 JDK 21。然而,在升级过程中,我们遇到了一个令人费解的现象:原本在 JDK 8 上运行正常的第三方接口调用,在 JDK 21 上却频繁抛出 SSLHandshakeException: Received fatal alert: handshake_failure。本文将详细复盘这次排查过程,揭示 JDK 21 在 SSL/TLS 握手层面的底层变化,并提供终极解决方案。
一、 故障现象:版本升级后的“断连”
在将开发环境切换至 JDK 21 后,我们的应用在调用特定政府网站接口(https://www.xxx.gov.cn)时发生了故障。
错误日志:
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
诡异现象:
JDK 8: 运行完美,无任何报错。
JDK 21: 死活连不上,且不报证书错误(CertificateException),直接握手失败。
浏览器/Postman: 可以正常访问。
这种“代码没变,环境变了”的差异,直接指向了 JDK 版本间的底层安全策略差异。
二、 排查之路:层层递进的“破案”过程
为了定位问题,我们开启 -Djavax.net.debug=ssl:handshake 调试模式,通过分析握手日志,我们经历了三个阶段的认知升级。
第一阶段:误判为“弱加密算法” —— 方案失效
直觉: JDK 21 更安全,默认禁用了弱算法。我们猜测是服务器使用了 MD5 或 1024 位密钥。
行动: 修改 java.security 文件,删除 jdk.tls.disabledAlgorithms 中的限制,甚至加上了 ECDH(误以为是禁用了 ECDH 导致的)。
结果: 无效。 即使放宽了所有限制,握手依然失败。这说明问题不在“禁用列表”,而在“协商过程”。
第二阶段:聚焦“椭圆曲线” —— 关键线索
在仔细比对 ClientHello(客户端发起)和 ServerHello(服务器回复)的日志时,我们发现了异常。
在服务器的 ECDHServerKeyExchange 消息中,出现了如下关键字段:

"ECDH ServerKeyExchange": {
  "parameters": {
    "named group": "x448"  // 服务器强制要求使用 x448 曲线
  }
}
分析:
JDK 21 的 ClientHello: 默认优先发送 x25519 和 secp256r1(P-256)。
服务器的回应: 该政府服务器配置较为特殊(可能为了高性能或特定合规),它无视了客户端的偏好,强制选择了 x448 (Curve448) 算法进行密钥交换。
矛盾点: 虽然 JDK 21 理论上支持 x448,但在某些构建版本或特定的握手上下文中,客户端无法处理服务器强制下发的 x448 参数,导致计算共享密钥失败。
三、 根本原因 (Root Cause)
这次故障的根本原因并非简单的“证书错误”或“协议不支持”,而是“算法偏好不匹配”:
服务器端的“死板”: 目标服务器(szpsq.gov.cn)在握手时,强制指定了 named group 为 x448。
客户端的“不适”: JDK 21 客户端在收到此指令后,由于环境配置或算法库实现的细微差异,无法完成基于 x448 的密钥计算。
JDK 8 为什么能行? Java 8 根本不认识 x448(Java 11+ 才引入),服务器检测到 JDK 8 的 ClientHello 后,会自动降级使用传统的 secp256r1,从而避开了这个坑。
四、 解决方案:强制“降级”协商
既然问题出在服务器强制使用 x448,那么解决方案就是不让服务器有机会选择 x448。我们通过 JVM 参数,强制客户端只支持通用的 secp256r1。
✅ 最终生效方案
在 IDEA 运行配置或 Tomcat 启动脚本的 VM Options 中,添加以下参数:
-Djdk.tls.namedGroups="secp256r1"
方案解析
原理: 该参数强制 JVM 在发送 ClientHello 时,只在 supported_groups 扩展中声明支持 secp256r1。
效果: 服务器收到请求后,发现自己想用的 x448 客户端不支持,无奈之下只能选择次优但通用的 secp256r1 进行握手。
结果: 握手成功,数据正常获取。
五、 经验总结与建议
不要盲目放宽限制: 遇到 handshake_failure,不要第一时间去修改 java.security 里的 disabledAlgorithms,这往往是性能或兼容性问题,而非安全性拦截。
善用 Debug 日志: -Djavax.net.debug=ssl:handshake 是排查 SSL 问题的瑞士军刀。重点关注 ClientHello 发了什么,ServerHello 回了什么,以及 named group 的协商过程。
老旧服务器的兼容性: 国内部分政府或银行网站(.gov.cn)服务器配置更新滞后或使用了特殊算法(如 x448 或国密),在迁移到 JDK 11/17/21 时,大概率需要通过 -Djdk.tls.namedGroups 或代码层定制 SSLParameters 来强制兼容。

注: 技术升级往往伴随着“暗坑”,这次排查虽然曲折,但也让我们更深入理解了 TLS 握手的底层细节。希望这篇复盘能帮后来者节省几个小时的排查时间。

来源:Heck's Blog
地址:https://www.heckjj.com/post/682/
转载时须以链接形式注明作者和原始出处及本声明,否则将追究法律责任,谢谢配合!
阅读(42) | 评论(0) | 引用(0)