今天在项目遇到了Received fatal alert: handshake_failure 的问题,直译就是握手失败了,说实话,我也是第一次碰到这种情况。在各大论坛,各种技术博客的海洋里,仍然没有解决我的问题。最后捡漏发现了这个原因,真的太出乎意料了。
首先和大家说一下网上的一些经验和处理办法,算是一个总结。
我们用curl 和 postman 发送请求,都可以握手成功,唯独用代码不能请求成功。handshake_failure的原因绝不是一种。
原因一: Https 的协议版本不一致。协议有 TLSv1.2,TLSv1.1,SSLv3
如何来确定这个问题?
1:如果你是在本机运行,可以这么做,写一个通讯客户端(只用来测试握手的)
代码是我临时引用别人的,我的在服务器上不好拿下来。
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class HttpsTest {
private static class TrustAnyTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
private static class TrustAnyHostnameVerifier implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
public static String sendHttps(String url) throws Exception {
InputStream in = null;
OutputStream out = null;
String returnValue = "";
try {
//SSLContext sc = SSLContext.getInstance("SSL");
// System.setProperty("https.protocols", "TLSv1.2,TLSv1.1,SSLv3");
// SSLContext sc = SSLContext.getInstance("TLS", "SunJSSE");
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
URL console = new URL(url);
HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
conn.setSSLSocketFactory(sc.getSocketFactory());
// conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
conn.setRequestMethod("POST");
conn.connect();
InputStream is = conn.getInputStream();
DataInputStream indata = new DataInputStream(is);
returnValue = indata.readLine();
conn.disconnect();
} catch (ConnectException e) {
e.printStackTrace();
throw e;
} catch (IOException e) {
e.printStackTrace();
throw e;
} finally {
try {
in.close();
} catch (Exception e) { }
try {
out.close();
} catch (Exception e) { }
}
return returnValue;
}
public static void main(String[] args) throws Exception {
System.out.println(sendHttps("https://finance-23055.beta.qunar.com"));
}
}
## 代码转载 https://blog.csdn.net/dzy_001/article/details/81012959
然后在运行参数里面设置
-Djavax.net.debug=all
然后运行,会出现下面的,内容很多,可以找到下面这些。
main, WRITE: TLSv1.2 Handshake, length = 191 ## 这是客户端发出的版本
[Raw write]: length = 196
main, READ: TLSv1.2 Alert, length = 2 ## 这是服务端返回的版本
main, RECV TLSv1.2 ALERT: fatal, handshake_failure
很遗憾,这不能解决我的问题,因为客户端服务端都是TLSv1.2。如果你们发现这两个版本不一样的话,你就属于这种情况了,如何解决,请看其他文章吧,我就不细说了,给两个推荐。
http://www.voidcn.com/article/p-xjnaqnwd-bqu.html
https://blog.csdn.net/u014644574/article/details/83381303
2:服务器不支持此协议
SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure
然后用
openssl s_client -connect finance-23055.beta.qunar.com:443 -state -debug -tls1 -msg
看到的message回复是这样:
read from 0xb644fc0 [0xb689760] (5 bytes => 5 (0x5))
0000 - 15 03 01 00 02 …..
read from 0xb644fc0 [0xb689765] (2 bytes => 2 (0x2))
0000 - 02 28
其中0x15代表的是alert,而0x28代表的handshake failure
这样很难发现服务器的支持协议,很慢,还要猜,还可能猜不准
https://www.ssllabs.com/ssltest/
进入这个地址,输入你要测试的服务器地址。然后就会出一个报告,很详细。
协议很清楚 看出是否支持。下面还有更加详细的,自己试试 吧。
3:SSL算法不支持
curl -d "" -k https://IP:port/tests -v
使用上述命令,我们可以清晰看到执行过程。
其中有一行是这样的
SSL connection using ******** ## * 代表了curl使用的算法
这个算法我们就是我们的了。那看我们的程序 是否支持这种算法呢?
ctx.getSupportedSSLParameters().getCipherSuites()
ctx.getSocketFactory().getDefaultCipherSuites()
ctx.getSocketFactory().getSupportedCipherSuites()
## ctx 是SSLContext 对象
打印一下,看curl的算法,是否在我们java的支持范围内。
解决办法请参考:https://blog.csdn.net/qq_36783371/article/details/80665901
4:也就是我这种了,最悲哀的吧。看我的上述代码了么,去掉这一段,就不会握手失败了。这个验证也是在握手过程内。
private static class TrustAnyHostnameVerifier implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
好了,这就是我总结的所有了。