<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title><![CDATA[Heck's  Blog]]></title> 
<link>https://www.heckjj.com/index.php</link> 
<description><![CDATA[一瞬间的决定，往往可以改变很多，事实上，让自己成功的往往不是知识，是精神！ 如果你总是为自己找借口，那只好让成功推迟。执行力，今天！]]></description> 
<language>zh-cn</language> 
<copyright><![CDATA[Heck's  Blog]]></copyright>
<item>
<link>https://www.heckjj.com/post//</link>
<title><![CDATA[从 JDK 8 到 JDK 21 的“握手”血泪史]]></title> 
<author>Heck &lt;@hecks.tk&gt;</author>
<category><![CDATA[编程杂谈]]></category>
<pubDate>Thu, 09 Apr 2026 09:02:37 +0000</pubDate> 
<guid>https://www.heckjj.com/post//</guid> 
<description>
<![CDATA[ 
	随着 Java 生态的演进，我们将核心项目从 JDK 8 迁移至 JDK 21。然而，在升级过程中，我们遇到了一个令人费解的现象：原本在 JDK 8 上运行正常的第三方接口调用，在 JDK 21 上却频繁抛出 SSLHandshakeException: Received fatal alert: handshake_failure。本文将详细复盘这次排查过程，揭示 JDK 21 在 SSL/TLS 握手层面的底层变化，并提供终极解决方案。<br/>一、 故障现象：版本升级后的“断连”<br/>在将开发环境切换至 JDK 21 后，我们的应用在调用特定政府网站接口（https://www.xxx.gov.cn）时发生了故障。<br/>错误日志：<br/>javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure<br/>诡异现象：<br/>JDK 8： 运行完美，无任何报错。<br/>JDK 21： 死活连不上，且不报证书错误（CertificateException），直接握手失败。<br/>浏览器/Postman： 可以正常访问。<br/>这种“代码没变，环境变了”的差异，直接指向了 JDK 版本间的底层安全策略差异。<br/>二、 排查之路：层层递进的“破案”过程<br/>为了定位问题，我们开启 -Djavax.net.debug=ssl:handshake 调试模式，通过分析握手日志，我们经历了三个阶段的认知升级。<br/>第一阶段：误判为“弱加密算法” —— 方案失效<br/>直觉： JDK 21 更安全，默认禁用了弱算法。我们猜测是服务器使用了 MD5 或 1024 位密钥。<br/>行动： 修改 java.security 文件，删除 jdk.tls.disabledAlgorithms 中的限制，甚至加上了 ECDH（误以为是禁用了 ECDH 导致的）。<br/>结果： 无效。 即使放宽了所有限制，握手依然失败。这说明问题不在“禁用列表”，而在“协商过程”。<br/>第二阶段：聚焦“椭圆曲线” —— 关键线索<br/>在仔细比对 ClientHello（客户端发起）和 ServerHello（服务器回复）的日志时，我们发现了异常。<br/>在服务器的 ECDHServerKeyExchange 消息中，出现了如下关键字段：<br/><br/>&quot;ECDH ServerKeyExchange&quot;: &#123;<br/>&nbsp;&nbsp;&quot;parameters&quot;: &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&quot;named group&quot;: &quot;x448&quot;&nbsp;&nbsp;// 服务器强制要求使用 x448 曲线<br/>&nbsp;&nbsp;&#125;<br/>&#125;<br/>分析：<br/>JDK 21 的 ClientHello： 默认优先发送 x25519 和 secp256r1（P-256）。<br/>服务器的回应： 该政府服务器配置较为特殊（可能为了高性能或特定合规），它无视了客户端的偏好，强制选择了 x448 (Curve448) 算法进行密钥交换。<br/>矛盾点： 虽然 JDK 21 理论上支持 x448，但在某些构建版本或特定的握手上下文中，客户端无法处理服务器强制下发的 x448 参数，导致计算共享密钥失败。<br/>三、 根本原因 (Root Cause)<br/>这次故障的根本原因并非简单的“证书错误”或“协议不支持”，而是“算法偏好不匹配”：<br/>服务器端的“死板”： 目标服务器（szpsq.gov.cn）在握手时，强制指定了 named group 为 x448。<br/>客户端的“不适”： JDK 21 客户端在收到此指令后，由于环境配置或算法库实现的细微差异，无法完成基于 x448 的密钥计算。<br/>JDK 8 为什么能行？ Java 8 根本不认识 x448（Java 11+ 才引入），服务器检测到 JDK 8 的 ClientHello 后，会自动降级使用传统的 secp256r1，从而避开了这个坑。<br/>四、 解决方案：强制“降级”协商<br/>既然问题出在服务器强制使用 x448，那么解决方案就是不让服务器有机会选择 x448。我们通过 JVM 参数，强制客户端只支持通用的 secp256r1。<br/>✅ 最终生效方案<br/>在 IDEA 运行配置或 Tomcat 启动脚本的 VM Options 中，添加以下参数：<br/>-Djdk.tls.namedGroups=&quot;secp256r1&quot;<br/>方案解析<br/>原理： 该参数强制 JVM 在发送 ClientHello 时，只在 supported_groups 扩展中声明支持 secp256r1。<br/>效果： 服务器收到请求后，发现自己想用的 x448 客户端不支持，无奈之下只能选择次优但通用的 secp256r1 进行握手。<br/>结果： 握手成功，数据正常获取。<br/>五、 经验总结与建议<br/>不要盲目放宽限制： 遇到 handshake_failure，不要第一时间去修改 java.security 里的 disabledAlgorithms，这往往是性能或兼容性问题，而非安全性拦截。<br/>善用 Debug 日志： -Djavax.net.debug=ssl:handshake 是排查 SSL 问题的瑞士军刀。重点关注 ClientHello 发了什么，ServerHello 回了什么，以及 named group 的协商过程。<br/>老旧服务器的兼容性： 国内部分政府或银行网站（.gov.cn）服务器配置更新滞后或使用了特殊算法（如 x448 或国密），在迁移到 JDK 11/17/21 时，大概率需要通过 -Djdk.tls.namedGroups 或代码层定制 SSLParameters 来强制兼容。<br/><br/>注： 技术升级往往伴随着“暗坑”，这次排查虽然曲折，但也让我们更深入理解了 TLS 握手的底层细节。希望这篇复盘能帮后来者节省几个小时的排查时间。
]]>
</description>
</item><item>
<link>https://www.heckjj.com/post//#blogcomment</link>
<title><![CDATA[[评论] 从 JDK 8 到 JDK 21 的“握手”血泪史]]></title> 
<author> &lt;user@domain.com&gt;</author>
<category><![CDATA[评论]]></category>
<pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate> 
<guid>https://www.heckjj.com/post//#blogcomment</guid> 
<description>
<![CDATA[ 
	
]]>
</description>
</item>
</channel>
</rss>