如何强制Android 4.x(API<20)应用使用TLSv1.2修复HTTPS通信问题?
解决Android 4.x(API<20)下Retrofit+OkHttp适配TLS 1.2的问题
我之前也碰到过完全一样的情况——后端升级到SHA-2证书和TLS 1.2后,Android 4.1到4.4的设备直接没法走HTTPS了,折腾了好一阵才搞定核心问题:API 16-19的Android系统默认没有启用TLS 1.2支持,哪怕硬件能扛,系统层也没开开关。下面给你一套可直接用的解决方案,包括自定义SocketFactory配置、OkHttp适配,还有可选的公钥固定建议:
1. 实现兼容TLS 1.2的SocketFactory
先写一个TLSSocketFactory,核心是强制SSLSocket启用TLS 1.2,同时兼容旧版本的TLS协议作为 fallback:
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; public class TLSSocketFactory extends SSLSocketFactory { private final SSLSocketFactory delegate; private final X509TrustManager trustManager; public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException { // 优先使用系统默认的信任管理器(比信任所有证书安全) this.trustManager = getDefaultTrustManager(); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, new TrustManager[]{trustManager}, null); this.delegate = sslContext.getSocketFactory(); } // 获取系统默认的信任管理器 private X509TrustManager getDefaultTrustManager() throws NoSuchAlgorithmException, KeyStoreException { TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init((KeyStore) null); return (X509TrustManager) trustManagerFactory.getTrustManagers()[0]; } @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 enableTlsProtocols(delegate.createSocket(s, host, port, autoClose)); } @Override public Socket createSocket(String host, int port) throws IOException, UnknownHostException { return enableTlsProtocols(delegate.createSocket(host, port)); } @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { return enableTlsProtocols(delegate.createSocket(host, port, localHost, localPort)); } @Override public Socket createSocket(InetAddress host, int port) throws IOException { return enableTlsProtocols(delegate.createSocket(host, port)); } @Override public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { return enableTlsProtocols(delegate.createSocket(address, port, localAddress, localPort)); } // 强制启用TLS 1.2,同时保留1.1和1.0作为兼容 fallback private Socket enableTlsProtocols(Socket socket) { if (socket instanceof SSLSocket) { ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.2", "TLSv1.1", "TLSv1"}); } return socket; } public X509TrustManager getTrustManager() { return trustManager; } }
2. 配置OkHttpClient使用自定义SocketFactory
接下来把这个SocketFactory集成到OkHttp里,还要给API<20的设备指定MODERN_TLS连接规范,确保优先用TLS 1.2:
import okhttp3.ConnectionSpec; import okhttp3.OkHttpClient; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.util.Collections; import java.util.concurrent.TimeUnit; public class OkHttpClientProvider { public static OkHttpClient getTls12EnabledClient() { OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS); try { TLSSocketFactory tlsSocketFactory = new TLSSocketFactory(); // OkHttp 3.x 需要同时传入SocketFactory和TrustManager clientBuilder.sslSocketFactory(tlsSocketFactory, tlsSocketFactory.getTrustManager()); // 只允许现代TLS协议,同时兼容旧设备 clientBuilder.connectionSpecs(Collections.singletonList(ConnectionSpec.MODERN_TLS)); } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) { e.printStackTrace(); // 初始化失败时,降级到默认配置 } return clientBuilder.build(); } }
3. 关联Retrofit和配置好的OkHttpClient
最后把这个定制的OkHttpClient传给Retrofit就行,和你平时的用法差不多:
import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class RetrofitProvider { private static final String BASE_URL = "https://your-backend-domain.com/"; public static YourApiService createApiService() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .client(OkHttpClientProvider.getTls12EnabledClient()) .addConverterFactory(GsonConverterFactory.create()) .build(); return retrofit.create(YourApiService.class); } }
可选:添加公钥固定(增强生产环境安全性)
虽然你暂时没加公钥固定,但强烈建议生产环境加上,防止中间人攻击。OkHttp自带证书固定功能,只需要把后端证书的公钥哈希配置进去:
// 在OkHttpClient.Builder里添加这段 clientBuilder.certificatePinner(new CertificatePinner.Builder() .add("your-backend-domain.com", "sha256/这里替换成你的证书公钥哈希") .build());
你可以用下面的openssl命令提取公钥哈希:
openssl s_client -connect your-backend-domain.com:443 | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
测试注意事项
- 一定要在真实的Android 4.x设备上测试,模拟器可能和真实设备的TLS支持有差异
- 如果还是失败,抓包看一下SSL握手的具体错误(比如证书不被信任、协议协商失败),针对性调整
内容的提问来源于stack exchange,提问作者Leem.fin




