错误信息
javax.net.ssl.SSLHandshakeException: (handshake_failure) Received fatal alert: handshake_failure
也可能输出为
- javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
- Received fatal alert: handshake_failure
JDK 21 及更高版本会在括号中显示告警名称:“(handshake_failure) Received fatal alert: handshake_failure”;JDK 8 和 11 只打印“Received fatal alert: handshake_failure”。
Java (JSSE) — TLS alert 40 received from the server
含义
服务器以告警 40(handshake_failure)中止了 TLS 握手。它在 JVM 的 ClientHello 中找不到任何可接受的选项(没有共同的密码套件、密钥交换组或签名算法),或者它要求出示客户端证书,而 JVM 没有出示。Java 只报告收到了这个告警;具体原因记录在服务器日志中,或者可以通过 javax.net.debug 看到。
原因
服务器只提供 JDK 已禁用的套件
常见当前的 JDK 更新版本(我们测试的是 21.0.12 和 25.0.4)在 jdk.tls.disabledAlgorithms 中列出了 TLS_RSA_*(RSA 密钥交换)、3DES、RC4 和匿名套件。只支持这些套件的旧服务器,与任何已更新到最新的 JVM 连接都会失败。
双向 TLS 缺少客户端证书
常见服务器请求了证书,而 JVM 没有可出示的证书(没有配置 javax.net.ssl.keyStore,或者其中没有由服务器接受的 CA 签发的证书)。
SNI 缺失或名称未知
偶见通过 IP 连接,或者设置了 jsse.enableSNIExtension=false,会导致 CDN 和多租户服务器中止握手。
非常旧的 JVM 连接现代服务器
偶见Java 7 或早期的 Java 8 更新版本缺少加固服务器所要求的 ECDHE/GCM 套件和默认启用的 TLS 1.2。
修复方法
- 1.
查看握手过程
javax.net.debug=ssl:handshake 会打印 ClientHello(所提供的套件)和告警;-XshowSettings:security:tls 会列出 JDK 启用的协议和套件。
bash # Show what the JVM offers and how the handshake ends java -Djavax.net.debug=ssl:handshake -jar app.jar 2>&1 | grep -E 'ClientHello|cipher_suites|Alert|fatal' # Settings in effect (protocols, suites) for this JDK java -XshowSettings:security:tls -version - 2.
服务器所有者:启用当前推荐的套件
TLS 1.3 和 TLS 1.2 搭配 ECDHE 以及 AES-GCM 或 ChaCha20,所有仍在维护的 JDK 都能使用。
text # Server side: offer ECDHE + AEAD suites that current JDKs support ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; - 3.
双向 TLS:配置客户端密钥库
客户端证书由 javax.net.ssl.keyStore(或基于 KeyManagerFactory 构建的 SSLContext)提供。
bash # Mutual TLS: give the JVM the client certificate and key java -Djavax.net.ssl.keyStore=client.p12 -Djavax.net.ssl.keyStorePassword=changeit \ -Djavax.net.ssl.keyStoreType=PKCS12 -jar app.jar - 4.
最后手段:为单个旧客户端调整 disabledAlgorithms
通过 -Djava.security.properties 传入的覆盖文件,可以为单个 JVM 从 jdk.tls.disabledAlgorithms 中去掉 TLS_RSA_*;复制完整的默认值,只删除你需要去掉的部分。这些套件不具备前向保密,因此这个例外的范围要尽量小,并且只能是临时的。
bash # Last resort for one legacy server: take this JDK's own value and drop only TLS_RSA_* v=$(sed -n '/^jdk.tls.disabledAlgorithms=/,/[^\\]$/p' "$JAVA_HOME/conf/security/java.security" | sed 's/\\$//' | tr -d '\n') printf '%s\n' "$v" | sed -E 's/, TLS_RSA_\*//' > legacy-rsa.security java -Djava.security.properties=legacy-rsa.security -jar legacy-client.jar
相关错误
No appropriate protocol (protocol is disabled or cipher suites are inappropriat…Java (JSSE)javax.net.ssl.SSLHandshakeException: (protocol_version) Received fatal alert: p…Java (JSSE)SSL routines:ssl3_read_bytes:ssl/tls alert handshake failureOpenSSL (client side)SSL routines:tls_post_process_client_hello:no shared cipherOpenSSL (server side: nginx, Apache, Postfix, openssl s_server)