HTTPS请求 Received fatal alert: handshake_failure异常---与众不同的原因

瘦欲@ 提交于 2019-11-29 06:42:39

今天在项目遇到了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;  
        }  
    } 

好了,这就是我总结的所有了。

 

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!