【问题说明】
使用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;
}
}