本文要解决的两类问题:
- 在Android4.1-Android5.0的系统上启用TLSv1.1和TLSv1.2
- java.lang.IllegalArgumentException: protocol TLSv1.1 is not supported
这两个问题比较具有代表性,在群里面讨论的时候见过的次数也是最多的,因此有些人遇到的问题也许协议名称不一样,但是本质都是类似的。
问题原因和解决思路分析
从 Android5.0 行为变更 可以看到 Android 5.0 开始默认启用了 TLSv1.1 和 TLSv1.2,但是从 Android 4.1 开始 TLSv1.1 和 TLSv1.2 其实就被支持了,只是默认没有启用而已。
我们最常用到的几种协议是SSLv3、TLSv1、TLSv1.1和 TLSv1.2,解决上面两个问题要搞清楚的是这几个协议在Android 系统中被支持的情况和被启用的情况,然后我们结合 minSdkVersion 和 targetSdkVersion 来选择协议即可,不同版本的 Android 系统对上述协议的支持情况:
客户端(SSLSocket)的支持情况:
协议 | 被支持(Api级别) | 被启用(Api级别) |
---|---|---|
SSLv3 | 1–25 | 1–22 |
TLSv1 | 1+ | 1+ |
TLSv1.1 | 16+ | 20+ |
TLSv1.2 | 16+ | 20+ |
服务端(SSLServerSocket)的支持情况:
协议 | 被支持(Api级别) | 被启用(Api级别) |
---|---|---|
SSLv3 | 1–25 | 1–22 |
TLSv1 | 1+ | 1+ |
TLSv1.1 | 16+ | 16+ |
TLSv1.2 | 16+ | 16+ |
数据来源:https://developer.android.com/reference/javax/net/ssl/SSLSocket
注意:这里说的客户端和服务端不是指Android端和JavaEE端/PHP端(还有Python、.NET等等),是指的在Android开发中的客户端Socket(SSLSocket)和服务端Socket(SSLServerSocket)。
到这里其实已经知道本文开始处的问题的原因了,TLSv1.1和TLSv1.2从Android4.1(Api级别16)开始才被支持,从Android4.4 Wear(Api级别20)才被启用(手机是Android5.0,Api级别21),因此在不同版本的Android系统中会出现需要被启用和启用时报不被支持的问题。
我们可以写一个TLS协议通用的兼容类,在所有的Android中强制启用已经被支持的所有协议,总结一下就是Android8.0及以上系统可以强制启用TLSv1、TLSv1.1和TLSv1.2协议,Android4.1-Android7.1.1系统可以强制启用SSLv3、TLSv1、TLSv1.1和TLSv1.2协议,其它版本系统可以强制启用SSLv3和TLSv1协议。
综上所述,如果开发者使用SSLv3协议,那么minSdkVersion不限制,targetSdkVersion不高于25,并且需要尽快更新为TLSv1.1协议或者TLS1.2协议;如果开发者使用TLSv1.1协议或者TLSv1.2协议,那么minSdkVersion应该不低于16,targetSdkVersion不限制;如果开发者使用TLSv1协议,那么目前不受限制。
代码实现
我们需要让SSLSocket启用对应的协议,代码对应的方法是:
1 |
SSLSocket#setEnabledProtocols(String[]); |
因此我们需要先在不同版本的Android系统中生成不同的协议数组:
1 2 3 4 5 6 7 8 9 10 11 |
private static final String PROTOCOL_ARRAY[]; static { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { PROTOCOL_ARRAY = new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"}; } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { PROTOCOL_ARRAY = new String[]{"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"}; } else { PROTOCOL_ARRAY = new String[]{"SSLv3", "TLSv1"}; } } |
SSLSocket是由SSLSocketFactory负责生成的,我们再写一个SSLSocketFactory的包装类,主要代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
public class TLSSocketFactory extends SSLSocketFactory { private static final String PROTOCOL_ARRAY[]; static { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { PROTOCOL_ARRAY = new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"}; } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { PROTOCOL_ARRAY = new String[]{"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"}; } else { PROTOCOL_ARRAY = new String[]{"SSLv3", "TLSv1"}; } } private SSLSocketFactory delegate; /** * 默认构造方法,直接生成启用所有协议的SSLSocket。 */ public TLSSocketFactory() { try { SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, new TrustManager[]{DEFAULT_TRUST_MANAGERS}, new SecureRandom()); delegate = sslContext.getSocketFactory(); } catch (GeneralSecurityException e) { throw new AssertionError(); // The system has no TLS. Just give up. } } /** * 包装SSLSocketFactory的构造方法,让外部生成的SSLScoket启用所有协议。 */ public TLSSocketFactory(SSLSocketFactory factory) { this.delegate = factory; } /** * 如果是SSLSocket,启用所有协议。 */ private static void setSupportProtocolAndCipherSuites(Socket socket) { if (socket instanceof SSLSocket) { ((SSLSocket) socket).setEnabledProtocols(PROTOCOL_ARRAY); } } // TODO 下面省去每一个createSocket()方法调用setSupportProtocolAndCipherSuites()方法的代码。 } |
iOS客户端支持情况
iOS 客户端(SSLSocket)的支持情况:
TLS 1.2 从 iOS 5 开始支持(TLS 1.2 was first added to iOS in iOS 5)
另外 对于在 2020 年 9 月 1 日格林尼治标准时间/世界标准时间 00:00 或之后颁发的 TLS 服务器证书,其有效期不得超过 398 天。