https协议和加密套件

【问题说明】
使用OkHttp请求https在android4.4报错SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure
【原因分析】
看报错是https请求失败了,失败一般分为两个原因,要么协议不支持,要么加密方式不支持。于是我用sslscan工具扫一下后台的域名,扫描结果如下图所示:

image

图我没有截全,上图只显示了服务器支持的tls1.2协议的加密套件,前面还有tls1.0和tls1.1的,但是加密方式都只支持ECC。上网简单查了下,https证书分为ECC加密证书和RSA加密证书两种,证书决定了加密方式,ECC相比RSA来说具有更轻、更快的特点,但是兼容性存在问题,需要比较高版本的系统和浏览器才支持,所以市面上大多SSL/TLS证书都采用的是RSA方式加密。下图是ECC对系统的要求:

image

可以发现android4.4是支持的,那就说明这个问题可以通过改终端软件解决,否则只能让服务器换一个RSA加密的证书了。
前面说了,根据报错,大概定位为协议不支持或者加密方式不支持
下面开始尝试解决
首先假如是协议不支持,因为网上有人说android4.4上的okhttp默认走的是sslv3协议,于是我设置https请求强制走tls1.2协议,结果仍然报错。文章末尾会给出强制走tls1.2的代码。
所以应该就是加密方式不支持,于是尝试自定义加密套件,发现好了。
总结一下,这个问题是由于okhttp默认的加密套件没有包含ecc加密方式导致的。
【解决方案】
下面是自定义https的加密套件完整的代码。

import android.os.Build;
import java.security.SecureRandom;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import okhttp3.CipherSuite;
import okhttp3.ConnectionPool;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import okhttp3.TlsVersion;
import okhttp3.internal.Util;

public class ComHttpClient {
    public OkHttpClient client;
    public ComHttpClient() {
        init();
    }
    // 忽略Https证书
    public void init() {
        int CONNECTION_POOL_MAX_IDEL = 20;
        int CONNECTION_POOL_KEEP_ALIVE = 20;
        ConnectionPool connectionPool = new ConnectionPool(CONNECTION_POOL_MAX_IDEL, CONNECTION_POOL_KEEP_ALIVE, TimeUnit.MINUTES);
        TrustAllManager trustAllManager = new TrustAllManager();
        int READ_TIMEOUT = 15000;
        int CONNECT_TIMEOUT = 15000;
        OkHttpClient.Builder builder = new OkHttpClient().newBuilder()
                .connectionPool(connectionPool)
                .connectTimeout(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
                .readTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS)
                .hostnameVerifier(createTrustAllHostnameVerifier());
        this.client = init(builder,trustAllManager);
    }

    protected OkHttpClient init(OkHttpClient.Builder builder, TrustAllManager trustAllManager) {
        SSLSocketFactory ssfFactory = null;
        try {
            if (Build.VERSION.SDK_INT <=Build.VERSION_CODES.KITKAT) {
                //低于或等于android4.4的版本自定义加密套件
                ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.COMPATIBLE_TLS)
                        .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
                        .cipherSuites(
                                CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384,
                                CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
                                CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
                                CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
                                CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
                                CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
                                CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
                                // maximum interoperability
                                CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
                                CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
                                // additionally
                                CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
                                CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
                                CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
                                CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
                                CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
                        )
                        .build();
                List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(
                        spec,ConnectionSpec.CLEARTEXT);
                builder.connectionSpecs(DEFAULT_CONNECTION_SPECS);
                /*//https请求强制走TVLSv1.2协议
                SSLContext sc = SSLContext.getInstance("TLSv1.2");
                sc.init(null, null, null);
                ssfFactory = new SSLSocketFactoryCompat(sc.getSocketFactory());
                builder.sslSocketFactory(ssfFactory, trustAllManager);*/
                SSLContext sc = SSLContext.getInstance("TLS");
                sc.init(null, new TrustManager[]{trustAllManager}, new SecureRandom());
                ssfFactory = sc.getSocketFactory();
                builder.sslSocketFactory(ssfFactory, trustAllManager);
            }else{
                SSLContext sc = SSLContext.getInstance("TLS");
                sc.init(null, new TrustManager[]{trustAllManager}, new SecureRandom());
                ssfFactory = sc.getSocketFactory();
                builder.sslSocketFactory(ssfFactory, trustAllManager);
            }
        } catch (Exception e) {
            Logcat.e("createTrustAllSSLFactory_err");
            e.printStackTrace();
        }
        return builder.build();
    }

    //获取HostnameVerifier
    protected HostnameVerifier createTrustAllHostnameVerifier() {
        return new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
    }
}

使用方式如下

 OkHttpClient okHttpClient = new ComHttpClient().client;

下面是相关的一些类

import android.annotation.SuppressLint;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;

public class TrustAllManager implements X509TrustManager {
    @SuppressLint("TrustAllX509TrustManager")
    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) {
    }

    @SuppressLint("TrustAllX509TrustManager")
    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType)  {
    }
    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[]{};
    }
}
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

public class SSLSocketFactoryCompat extends SSLSocketFactory {
    private static final String[] TLS_V12_ONLY = {"TLSv1.2"};
    final SSLSocketFactory delegate;
    public SSLSocketFactoryCompat(SSLSocketFactory base) {
        this.delegate = base;
    }
    @Override
    public String[] getDefaultCipherSuites() {
        return delegate.getDefaultCipherSuites();
    }
    @Override
    public String[] getSupportedCipherSuites() {
        return delegate.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        return patch(delegate.createSocket(s, host, port, autoClose));
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        return patch(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
        return patch(delegate.createSocket(host, port, localHost, localPort));
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        return patch(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        return patch(delegate.createSocket(address, port, localAddress, localPort));
    }

    private Socket patch(Socket s) {
        if (s instanceof SSLSocket) {
            ((SSLSocket) s).setEnabledProtocols(TLS_V12_ONLY);
        }
        return s;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容