项目中有一个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不开启加密

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

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

上一篇 下一篇