SSLHandshakeException:收到致命警报:Java 6 -> 8 升级后的握手失败
Posted
技术标签:
【中文标题】SSLHandshakeException:收到致命警报:Java 6 -> 8 升级后的握手失败【英文标题】:SSLHandshakeException: Received fatal alert: handshake_failure after Java 6 -> 8 upgrade 【发布时间】:2015-11-19 09:47:32 【问题描述】:我们最近将一个项目从 Java 6 更新到 Java 8,现在我们在 SSL 握手方面遇到了障碍。
服务层使用客户端请求和接收来自第三方应用程序的调用。在服务层中,使用
初始化密钥库 System.setProperty("javax.net.ssl.trustStore", keyStoreFile);
System.setProperty("javax.net.ssl.trustStorePassword", keyStorePassword);
并通过 applicationContext.xml 注入:
<property name="keyStoreFile" value="/keystore/keystore.keystore" />
<property name="keyStorePassword" value="password" />
客户端应该信任所有证书以防出错:
private void trustHttpsCertificates() throws Exception
try
Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[]
new X509TrustManager()
public X509Certificate[] getAcceptedIssuers()
return new X509Certificate[0];
public void checkServerTrusted(X509Certificate[] certs, String authType)
public void checkClientTrusted(X509Certificate[] certs, String authType)
;
// Ignore differences between given hostname and certificate hostname
HostnameVerifier hv = new HostnameVerifier()
public boolean verify(String hostname, SSLSession session) return true;
;
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(hv);
catch (KeyManagementException e)
String errorMsg = "client initialization error: " + e.getMessage();
log.error(errorMsg);
throw new Exception(errorMsg, e);
我在升级过程中将上面的“SSL”更改为“TLSv1”,因为 Java 8 不支持 SSL。这可能是个问题吗?
调试日志
Allow unsafe renegotiation: false
Allow legacy hello messages: true
Is initial handshake: true
Is secure renegotiation: false
http-nio-9080-exec-6, setSoTimeout(0) called
Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 for TLSv1
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 for TLSv1
Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 for TLSv1.1
Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 for TLSv1.1
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 for TLSv1.1
Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 for TLSv1.1
Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 for TLSv1.1
%% No cached client session
*** ClientHello, TLSv1.2
RandomCookie: GMT: 1423711122 bytes = 237, 188, 53, 112, 79, 112, 248, 92, 164, 127, 178, 34, 205, 40, 245, 25, 77, 143, 116, 126, 203, 96, 61, 181, 114, 148, 66, 227
Session ID:
Cipher Suites: [TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
Compression Methods: 0
Extension elliptic_curves, curve names: secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA224withECDSA, SHA224withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA, MD5withRSA
***
[write] MD5 and SHA1 hashes: len = 237
// ...
http-nio-9080-exec-6, READ: TLSv1 Alert, length = 2
http-nio-9080-exec-6, RECV TLSv1.2 ALERT: fatal, handshake_failure
http-nio-9080-exec-6, called closeSocket()
http-nio-9080-exec-6, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
// ...
at java.lang.Thread.run(Thread.java:745)
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:2023)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1125)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387)
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:563)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1512)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1440)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:338)
想法
与 Java 6 的旧版本相比,主要有两点突出:
在旧版本的日志中,当尝试成功时,它会清晰地显示正确的证书:
http-9080-1, READ: TLSv1 Handshake, length = 1375
*** Certificate chain
chain [0] = [
[
Version: V3
Subject: xxx
Signature Algorithm: MD5withRSA
Key: Sun RSA public key, 1024 bits
Validity: [From: Wed May 26 14:31:31 CEST 1999,
To: Thu May 25 14:31:31 CEST 2000]
Issuer: xxx
SerialNumber: [ 01]
]
Algorithm: [MD5withRSA]
这在新的 Java 8 版本中不会发生。
另外,TLSv1 vs TLSv1.2?
http-nio-9080-exec-6, READ: TLSv1 Alert, length = 2
http-nio-9080-exec-6, RECV TLSv1.2 ALERT: fatal, handshake_failure
是说我正在尝试连接 TLSv1.2?还是 TLSv1?而且不被接受?我真的不明白。有没有办法找出服务器接受哪些 TLS 版本?
我尝试在启动时添加标志:
-Dhttps.protocols=TLSv1
-Ddeployment.security.TLSv1=true
-Djavax.net.ssl.keyStore=C:\keystore\keystore.keystore
-Djavax.net.ssl.keyStorePassword=password
-Djavax.net.ssl.trustStore=C:\keystore\keystore.keystore
-Djavax.net.ssl.trustStorePassword=password
还以编程方式添加密钥库管理器:
KeyStore ks = KeyStore.getInstance("JKS");
InputStream ksIs = new FileInputStream("/keystore/keystore.keystore");
try
ks.load(ksIs, password.toCharArray());
catch (Exception e)
e.printStackTrace();
finally
if (ksIs != null)
try
ksIs.close();
catch (IOException e)
e.printStackTrace();
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password.toCharArray());
然后用它初始化 SSLContext:
sc.init(kmf.getKeyManagers(), trustAllCerts, new SecureRandom());
但问题仍然存在。有人帮忙吗?
【问题讨论】:
这个***.com/a/34296145/185565 解决方案适用于JDK7 和JDK8。使用外观 SSL 工厂,然后自定义主机名验证器可用于 SSL 连接。您可以包装sc.getSocketFactory()
实例并且应该可以工作。
【参考方案1】:
好的,我们现在已经开始工作了。我会在这里发布答案,以防有一天有人需要它。
我们已经尝试了很多东西,所以我不确定实际需要做什么才能让它发挥作用,但这里有一些我们在这个过程中改变的东西。
-Dhttps.protocols=SSLv3,TLSv1,SSLv2Hello
添加此标志会导致证书出现在 javax.net.debug
日志中,但我们仍然收到 SSLHandshakeException
。似乎服务器会接受的唯一密码是SSL_RSA_WITH_RC4_128_MD5
。这不是我们的客户自动选择的。
-Dhttps.cipherSuites=SSL_RSA_WITH_RC4_128_MD5
我们添加了这个标志来限制客户端的密码套装。连同以编程方式设置相同的限制(不确定是否需要两者):
socket.setEnabledCipherSuites(new String[] "SSL_RSA_WITH_RC4_128_MD5");
将可用密码套件限制为客户端可以使用的唯一密码套件,让客户端选择该密码套件。
我们还对jre/lib/security/java.security
文件进行了以下更改,以启用SSLv3
和SSL_RSA_WITH_RC4_128_MD5
密码:
jdk.tls.disabledAlgorithms
中删除SSLv3
将SSL_RSA_WITH_RC4_128_MD5
添加到jdk.tls.legacyAlgorithms
这可能不建议用于生产服务器,因为SSLv3
已过时,而且密码非常陈旧且过时,但在这种情况下,安全性不是一个大问题(内部应用程序使用)。
这些帖子对我也有帮助:
SSL connection failing for Java 7 Java 7 (acting as client) SSL handshake failure with keystore and truststore that worked in Java 6 Received fatal alert: handshake_failure through SSLHandshakeException Java cipher suites【讨论】:
有趣,我有类似的问题 Java7 很好,但 Java8 失败了。我使用HttpsURLConnection.setSSLSocketFactory(..), HttpsURLConnection.setHostnameVerifier(new DebugAllowAllVerifier())
代码。删除验证者解决了一个问题。我不知道为什么一个简单的return true
验证器会出现问题。
这个***.com/a/34296145/185565 解决方案使用外观 SSLFactory 和 setHostnameVerifier() 一起工作。奇怪,因为它什么都不做,但在 JDK7+JDK8 上仍然可以。【参考方案2】:
String testURL = "https://api.chargeio.com/status";
SSLContext sslcontext = SSLContext.getInstance("TLSv1.2");
sslcontext.init(null, null, null);
try
SSLConnectionSocketFactory socketFactory = new
SSLConnectionSocketFactory(sslcontext,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); // Socket
HttpClient client =
HttpClients.custom().setSSLSocketFactory(socketFactory).build();
HttpGet httpget = new HttpGet(testURL);
HttpResponse response = client.execute(httpget);
System.out.println(EntityUtils.toString(response.getEntity()));
System.out.println("Response Code (Apache): " + response.getStatusLine().getStatusCode());
catch (Exception e)
System.err.println("HttpsURLConnection Failed");
e.printStackTrace();
【讨论】:
【参考方案3】:我的猜测是你的服务器太坏了,无法正确处理 TLS 1.2 握手。通常,不理解 TLS 1.2 的服务器应该回复它可以使用的最佳版本,但您的服务器没有。
存在此类损坏的服务器,浏览器尝试通过使用较低的 TLS 版本重试来解决这些问题。在浏览器之外,这些重试并不常见,因此这些客户端只会失败。
虽然我不能确定服务器是否损坏了 15 年前过期的证书并使用长期损坏的 MD5 算法签名,但这表明您必须处理一个非常古老且被忽视的安装。因此,原始服务器的开发人员很有可能从未想过可能存在 TLS 1.2 之类的东西,或者它会在 TLS 1.2 握手中使用的 TLS 扩展之一上发牢骚。
由于此问题与证书的验证无关,所有通过在验证领域摆弄来解决问题的尝试都是徒劳的。如果您强制使用 TLS 1.1 或 TLS 1.1 而不是 TLS 1.2,您可能会取得更大的成功。您可以尝试使用-Dhttps.protocols=TLSv1,TLSv1.1
或-Dhttps.protocols=TLSv1
设置来执行此操作。
【讨论】:
感谢您的回复。这也是我的猜测。 “ClientHello,TLSv1.2”是否意味着我的客户端正在尝试使用 TLSv1.2?正如您从我的原始帖子中看到的那样,我已经尝试过使用 -Dhttps.protocols=TLSv1 但它并没有改变任何东西。是否有任何其他方式,以编程方式或使用标志来强制执行 TLSv1(甚至可能是 SSLv3(我知道,它不应该使用,但在这种情况下,安全性并不是什么大问题))? @JesperLehtinen:是的,客户端进行了 TLS 1.2 握手。实际上可能是“TLSv1”包含更高版本,文档在这方面不是很具体。对于这个旧且不安全的服务器,您实际上可能会尝试使用“SSLv3”——我怀疑这会进一步降低该服务器的安全性。 感谢 Steffen 的帮助,现在它又可以正常工作了。我将在新答案中发布解决方案和后续行动。以上是关于SSLHandshakeException:收到致命警报:Java 6 -> 8 升级后的握手失败的主要内容,如果未能解决你的问题,请参考以下文章
当程序在 IntelliJ 中运行时,为啥我会收到 SSLHandshakeException 作为 JAR?
Apple Pay 证书更新 | SSLHandshakeException:收到致命警报:handshake_failure
ChangeCipherSpec - javax.net.ssl.SSLHandshakeException:收到致命警报:handshake_failure
无法下载站点地图:SSLHandshakeException:收到致命警报:handshake_failure
javax.net.ssl.SSLHandshakeException:收到致命警报:handshake_failure
获取 javax.net.ssl.SSLHandshakeException:收到致命警报:handshake_failure 错误