欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java调用ssl异常(javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or ci

程序员文章站 2022-03-04 13:09:03
...

首发地址、原文链接:https://www.hsmus.top/202107284.html
项目中有一个HttpUtil作为客户端,当请求https的时候,会出现:

javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)

的问题,本文用作者已有的知识认知排查并修复此问题。

目前网上的解决方案尝试之后无果,https://www.cnblogs.com/andy-alone/p/10937311.html。
https://blog.csdn.net/Wing_kin666/article/details/116449722

-1 先把解决方案放上:

    public static String httpsPost(String procid, String url, String params, int timeOut) {
        // Trust all CA
        HttpPost httpPost = new HttpPost(url);
        String result = null;
        CloseableHttpClient httpClient = null;
        try {
            SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(null, new TrustStrategy() {
                public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                    return true;
                }
            }).build();
            // Set supportedProtocols
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1","TLSv1.2"}, null,
                    SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
            // 根据默认超时限制初始化requestConfig
            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(timeOut).setConnectTimeout(timeOut).build();


            // 得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
            StringEntity postEntity = new StringEntity(params, "UTF-8");//"UTF-8");
            httpPost.addHeader("Content-Type", "application/json");//"application/x-www-form-urlencoded");
            httpPost.setEntity(postEntity);
            // 设置请求器的配置
            httpPost.setConfig(requestConfig);

            HttpResponse response = httpClient.execute(httpPost);

            HttpEntity entity = response.getEntity();

            result = EntityUtils.toString(entity, "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (httpPost != null) {
                    httpPost.releaseConnection();
                }
                if (httpClient != null) {
                    httpClient.close();
                }
            } catch (IOException e) {
                LogUtil.addProclog(procid, CommonUtil.getTrace(e) + "\r\n");
            }
        }

        return result;
    }

1. 原因分析

1. 从百度翻译之后得知具体原因为:

javax.net.ssl.SSLHandshakeException:没有合适的协议(协议被禁用或密码套件不合适)

2. 再去查阅HTTPS握手的过程:

参考资料:https://www.cnblogs.com/jjzd/p/9346260.html

2.1 SSL握手大致过程:
  1. 客户端发送随机数1,支持的加密方法(如RSA公钥加密)

  2. 服务端发送随机数2,和服务器公钥,并确认加密方法

  3. 客户端发送用服务器公钥加密的随机数3

  4. 服务器用私钥解密这个随机数3,用加密方法计算生成对称加密的**给客户端,
    接下来的报文都用双方协定好的加密方法和**,进行加密

** SSL握手详细过程:**
一、客户端发出加密通信请求ClientHello
提供:
1,协议版本(如TSL1.0)
2,随机数1(用于生成对话**)
3,支持的加密方法(如RSA公钥加密)
4,支持的压缩方法

二、服务器回应SeverHello
回应内容:
1,确认使用的加密通信协议版本(TSL1.0)
2,随机数2(用于生成对话**)
3,确认加密方法(RSA)
4,服务器证书(包含非对称加密的公钥)
5,(可选)要求客户端提供证书的请求

三、客户端验证证书
如果证书不是可信机构颁布,或证书域名与实际域名不符,或者证书已经过期,就会向访问者显示一个警告,是否继续通信

四、客户端回应
证书没有问题,就会取出证书中的服务器公钥
然后发送:
1,随机数3(pre-master key,此随机数用服务器公钥加密,防止被窃听)
2,编码改变通知(表示随后的信息都将用双方商定的方法和**发送)
3,客户端握手结束通知

五、双方生成会话**
双方同时有了三个随机数,接着就用事先商定的加密方法,各自生成同一把“会话**”
服务器端用自己的私钥(非对称加密的)获取第三个随机数,会计算生成本次所用的会话**(对称加密的**),如果前一步要求客户端证书,会在这一步验证

六、服务器最后响应
服务器生成会话**后,向客户端发送:
1,编码改变通知(后面的信息都用双方的加密方法和**来发送)
2,服务器握手结束通知

至此,握手阶段全部结束,接下来客户端与服务器进入加密通信,用会话**加密内容

2.2 分析

根据以上的 2.1.2可知,这里客户端跟服务器确认加密算法是否支持,所以为了解决我们遇到的问题,就要知道服务器可以用什么算法,以便于我们在构建客户端的时候配置响应的加密算法,想要知道服务器用什么算法,我们可以在浏览器打开目标网站,在 控制台 – 安全(security) 里面查看。

在前面的代码中

// Set supportedProtocols 
 SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1","TLSv1.2"}, null,
                    SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

// ---------------- 以下为SSLConnectionSocketFactory的构造方法之一
    public SSLConnectionSocketFactory(
            final SSLContext sslContext,
            final String[] supportedProtocols,
            final String[] supportedCipherSuites,
            final X509HostnameVerifier hostnameVerifier) {
        this(Args.notNull(sslContext, "SSL context").getSocketFactory(),
                supportedProtocols, supportedCipherSuites, hostnameVerifier);
    }

根据 SSLConnectionSocketFactory的构造方法可以知道,在二个参数可以设置加密算法,本项目之前是指定了只支持new String[]{"TLSv1"}这一种算法,但是服务器上支持的是TLSv1.2的算法,导致算法不匹配而报错。

知道了原因之后,有两种解决方案:

  1. 增加一个支持的加密算法 new String[]{"TLSv1","TLSv1.2"}
  2. 把第二个参数设为null不开启加密

那么为了安全起见,第二种方式还是需要慎重考虑,本文采用了第一种方案,且成功解决此异常。

学疏才浅,有问题恳请在评论区指出~

相关标签: Java java java-ee