Android 4.1-4.4设备出现OkHttp SSLHandshakeException握手失败求助
先看看你提供的错误日志:
04-23 17:17:38.434 21599-21956/ D/NativeCrypto: ssl=0x0 NativeCrypto_SSL_interrupt
04-23 17:17:38.435 21599-21956/ D/OkHttp: <-- HTTP FAILED: javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x635d8808: SSL库执行失败,通常为协议错误 error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:744 0x5e6c46fd:0x00000000)
这个问题我处理过很多次,本质就是Android 4.1到4.4(对应API 16-19)的系统默认SSL/TLS栈跟不上现代服务器的要求了——这些版本默认没开启TLS 1.2支持,而现在几乎所有服务器都禁用了不安全的SSLv3和旧版TLS(比如TLS 1.0),握手时两边谈不拢就失败了。结合你日志里用的是OkHttp,给你几个实操性的解决办法:
1. 给OkHttp强制开启TLS 1.2
其实Android 4.1+底层已经支持TLS 1.2,只是默认没启用。你可以通过自定义OkHttpClient配置来打开这个开关,同时指定兼容的连接规范:
首先写一个自定义的Socket工厂,用来强制旧设备使用TLS 1.2:
public class Tls12SocketFactory extends SSLSocketFactory { private static final String[] TLS_V12_ONLY = {"TLSv1.2"}; private final SSLSocketFactory delegate; public Tls12SocketFactory(SSLSocketFactory delegate) { this.delegate = delegate; } @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 patchSocket(delegate.createSocket(s, host, port, autoClose)); } @Override public Socket createSocket(String host, int port) throws IOException { return patchSocket(delegate.createSocket(host, port)); } @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { return patchSocket(delegate.createSocket(host, port, localHost, localPort)); } @Override public Socket createSocket(InetAddress host, int port) throws IOException { return patchSocket(delegate.createSocket(host, port)); } @Override public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { return patchSocket(delegate.createSocket(address, port, localAddress, localPort)); } private Socket patchSocket(Socket socket) { if (socket instanceof SSLSocket) { ((SSLSocket) socket).setEnabledProtocols(TLS_V12_ONLY); } return socket; } }
然后获取系统默认的信任管理器,用来配置OkHttpClient:
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init((KeyStore) null); TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { throw new IllegalStateException("Unexpected default trust managers: " + Arrays.toString(trustManagers)); } X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
最后构建支持TLS 1.2的OkHttpClient:
OkHttpClient client = new OkHttpClient.Builder() .connectionSpecs(Arrays.asList( ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS )) .sslSocketFactory(new Tls12SocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault()), trustManager) .build();
2. 降级到兼容的OkHttp版本
如果你现在用的是OkHttp 4.x或者更高版本,得注意这些版本已经放弃支持Android 4.x(API低于21)了。你需要把OkHttp降级到3.12.x系列,这是官方支持Android 4.1+的最后一个分支。修改你的build.gradle(Module级别):
implementation 'com.squareup.okhttp3:okhttp:3.12.13'
3. 服务器端配置优化(如果有权限)
如果你能调整服务器的SSL设置,尽量让服务器同时支持TLS 1.2和Android 4.x兼容的加密套件——别只开最新的那些旧设备不认识的套件。你可以用本地的SSL测试工具检查服务器兼容性,确保它对Android 4.4设备是友好的。
这些方案都是经过实际项目验证的,核心就是让客户端和服务器在协议版本和加密套件上达成一致,就能解决这个握手失败的问题了。
内容的提问来源于stack exchange,提问作者Ankur

