https请求发生received fatal alert: handshake_failure; nested exception
HTTPS原理
https是一个加密传输协议,通过加密算法对报文进行加密传输确保传输的安全。https是通过非对称加密算法确认传输的密钥后使用对称加密算法进行信息的加密传输。https四次握手大概过程简述如下:
1、客户端请求建立连接,发送自己支持的算法方式以及一个随机数client random给服务器;
2、服务器选择其中客户端和服务端同时支持的加密算法,并且再加上另外一个随机数server random,和数字证书(其中有公钥),发送给客户端;
3、客户端确认这个数字证书是有效的,并且再生成一个新的随机数,将这个随机数用服务器发送给它的数字证书中的公钥进行加密发送给服务器;
4、服务器收到客户端的回复,利用自己的私钥进行解密,获得这个随机数,然后通过将前面这三个随机数以及他们协商的加密方式,计算生成一个对称密钥。
数据包传输如下
PS: 整个过程传递三个随机数做为加密钥匙,因为计算机做的是伪随机所以用三个随机数更加逼近真正的随机数。同时,https也不是绝对的安全,交换密钥过程容易遭到中间人攻击,中间人跟服务器握手交换密钥,之后再通过假证书和客户端交换密钥,这样就可以随意解密传输内容。
异常分析
received fatal alert: handshake_failure异常是在这个握手过程中出现异常,通过调试查看握手过程中的数据传输情况。
编译执行以下代码
import javax.net.ssl.*;
import java.io.*;
import java.net.URL;
/**
* @author edui 2022/8/11
*/
public class Main {
public static void main(String[] args) throws Exception {
Main main = new Main();
System.out.println("---------------------------------------------------------------------");
main.TestRiQingAPI_SaleOrder();
}
public void TestRiQingAPI_SaleOrder() throws Exception {
String postData = getJson();
String url = "https://xxxxxxxx/";
HttpsURLConnection conn = null;
OutputStream out = null;
String rsp = null;
byte[] byteArray = postData.getBytes("utf-8");
try {
URL uri = new URL(url);
conn = (HttpsURLConnection) uri.openConnection();
//忽略证书验证--Begin
conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
//忽略证书验证--End
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setRequestProperty("Host", uri.getHost());
conn.setRequestProperty("Content-Type", "application/json");
out = conn.getOutputStream();
out.write(byteArray);
out.close();
if(conn.getResponseCode()==200) {
rsp = getStreamAsString(conn.getInputStream(), "utf-8");
}else {
rsp = getStreamAsString(conn.getErrorStream(), "utf-8");
}
System.out.println(rsp);
} catch (Exception e) {
if(null!=out)
out.close();
e.printStackTrace();
}
}
/**
* getJson
*
*/
private static String getJson() {
return "{" + "\"name\"" + ":" + "\"robo_blogs_zh123\"" + "}";
}
private static String getStreamAsString(InputStream stream, String charset) throws IOException {
try {
Reader reader = new InputStreamReader(stream, charset);
StringBuilder response = new StringBuilder();
final char[] buff = new char[1024];
int read = 0;
while ((read = reader.read(buff)) > 0) {
response.append(buff, 0, read);
}
return response.toString();
} finally {
if (stream != null) {
stream.close();
}
}
}
class TrustAnyHostnameVerifier implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
}
增加如下jvm启动参数开启调试模式查看握手情况。
java -Djavax.net.debug=ssl Main
正常输出如下
报错输出如下
其中
ClientHello—客户端发送支持的加密算法套件CipherSuite
ServerHello—服务端选择客户端和服务端都支持的加密算法套件CipherSuite
可以发现服务端使用的加密算法套件CipherSuite是
TLS_RSA_WITH_AES_128_CBC_SHA
ps:
TLS----使用tls安全协议
RSA----使用RSA非对称加密来传输AES密钥
AES_128_CBC----应用数据使用AES128 CBC模式加密
SHA----摘要算法使用SHA
但是在异常的jvm里客户端发送的加密算法套件CipherSuite列表里并不包含该加密套件
所以出现异常是因为JDK客户端不支持服务器所使用的加密件套。
ps:可以看到 ClientHello和ServerHello后面都跟着TLS版本,网上很多人遇到版本不一致问题,JDK8默认使用TLSv1.2 ,JDK7默认使用TLSv1.1。
jdk8应该是包含该套件的
jdk8有相应的支持,那么加密算法的代码应该是包含在里面的,那可能就是被禁用了
安装路劲下
\jre\lib\security\java.security
图中的这行配置,看注释可以知道这个配置的作用是可以禁掉一些算法。在异常的客户端JDK上SHA1确实被禁掉了
解决办法
客户端去掉禁用SHA1或者服务端使用相同的JDK解决。
PS:SHA1算法是不安全的所以一些JDK会默认禁用