Android 5.0以下系统支持TLS 1.1/1.2协议版本
一、背景
项目中,客户端与服务端之间普遍使用Https协议通信,突然接到测试同事反馈Android5.0以下手机上,App测试服使用出现问题,出现SSL handshake aborted
错误信息,但正式服正常。经查,普遍错误信息详情如下:
SSL handshake aborted: ssl=0x78f08cd0: I/O error during system call, Connection reset by peer
....
复制代码
从错误信息上粗略看上去,SSL握手阶段出现问题,连接终止。
二、分析与处理
2.1 问题分析
从总体信息上看,显然测试服与正式服环境有所不同,导致在Android 5.0以下机型上SSL握手阶段失败。很有可能是测试服改变了相关配置。网上查了一圈,很快,Android官网文档上找到了对应指引。
developer.android.com/reference/j…
文档中的图示给出了Android版本与SSL/TLS版本之间的对应关系。
SSLSocket
来源于javax.net.ssl
,实际上是java的扩展库,Android不同系统版本中引入的是不同的Open JDK版本。因此,此处的版本对应关系的背后,实际上是因为java的不同版本中,对于SSLSocket
中对SSL/TLS版本的默认支持发生了变化。
同时,对于更底层的,如SSLSocketFactoryImpl、SSLSocketImpl等实现类,Oracle JD是在sun.security.ssl
包中,Open JDK对其进行了自己的实现,并放在了com.android.org.conscrypt
包中。并在类名前前统一加上了Open
加以区分,如OpenSSLSocketFactoryImpl
、OpenSSLSocketImpl
等。
对于conscrypt的介绍,可以参考文档:
source.android.google.cn/devices/arc…
Android 5.0 API级别是21,5.0以下常用的机型是4.4.x/4.2.x等。API Level 16对应的是Android 4.1。因此,问题基本上可以定位在服务端对TLS版本做了升级。
通过Https通信,客户端与服务端在SSL/TLS层建立安全连接前,涉及到版本协商过程。SSL/TLS在客户端和服务端分别具有对应的版本,握手阶段客户端与服务端的SSL/TLS版本,会取用两者同时支持的最高版本。如在Android 4.4手机上,默认支持SSLv3,TLSv1,如果服务端配置支持的协议版本是TLSv1,TLSv1.1,TLSv1.2,则会取用TLSv1作为协商后的版本。当然,无论是客户端还是服务端,对于SSL/TLS版本,在对应系统版本所能支持的协议版本范围内,是可以人为去修改的。如4.4系统手机上,可以将客户端在请求时的支持版本改成SSLv3,TLSv1,TLSv1.1,TLSv1.2。如果此时服务端支持的协议版本是TLSv1,TLSv1.1,TLSv1.2,协商后的版本将是TLSv1.2。
对于给定的Https的服务端网址,可以检测其当前所支持的SSL/TLS版本。
推荐一个非常实用的检测网站,不仅列出了服务端当前支持的版本,还列出了具体的加密套件等有用信息。
对项目中的正式服和测试服实测,结果如下:
正式服:
测试服:
显然,服务端在测试服中,将TLS1.0的版本支持给直接去掉了。这也正好与测试结果及从Android官方文档中分析结果是一致的。
经与服务端/运维等同事确认,测试服TLS版本协议确实做了修改,处于安全及升级等方面考虑,测试服运行一段时间后,后续也会同样部署到正式服中。
这也就意味着,客户端是需要适配的。
2.2 问题处理
当前项目最低支持版本是4.4,从Android官方文档中可以看出,Android 4.4默认支持的SSL/TLS版本是SSLv3,TLSv1。但TLSv1.1,TLSv1.2实际上也是在其支持范围内的,需要人为去配置。
我们的最终目标是改变改变SSLSocket
实例中的enabledProtocols
,具体可以通过调用其方法setEnabledProtocols(String protocols[])
。SSLSocket
,对外,是通过SSLSocketFactory
接口的方式与外部交互,其创建的调用方式,具体是通过createSocket()
方法进行。
因此,我们可以通过代理模式,设置兼容的SSLSocketFactory
,并重写其对应的createSocket()
方法,同时,将其设置给OkHttpClient
的sslSocketFactory
。
首先实现代理类:
public class TlsCompatSocketFactory extends SSLSocketFactory {
private static final String[] TLS_VERSION_LIST = {"TLSv1", "TLSv1.1", "TLSv1.2"};
final SSLSocketFactory target;
public TlsCompatSocketFactory(SSLSocketFactory target) {
this.target = target;
}
@Override
public String[] getDefaultCipherSuites() {
return target.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return target.getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return supportTLS(target.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return supportTLS(target.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return supportTLS(target.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return supportTLS(target.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return supportTLS(target.createSocket(address, port, localAddress, localPort));
}
private Socket supportTLS(Socket s) {
if (s instanceof SSLSocket) {
((SSLSocket) s).setEnabledProtocols(TLS_VERSION_LIST);
}
return s;
}
}
复制代码
然后在OkHttpClient的封装类中,将其设置给OkHttpClient的builder:
....
@JvmStatic
// 设置5.0以下机型可以支持TLS 1.1/1.2版本
val sc = SSLContext.getInstance("TLS")
sc.init(null, null, null)
clientBuilder.sslSocketFactory(TlsCompatSocketFactory(sc.socketFactory), object : X509TrustManager {
@Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
}
@Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
}
override fun getAcceptedIssuers(): Array<X509Certificate> {
return arrayOf()
}
})
....
复制代码
设置完成后,如下图所示,在进行Https请求时,sslSocketFactory
已经被替换成了我们自己定义的代理类TlsCompatSocketFactory
。其内部的target
对象中的sslParameters
的enabledProtocols
为TLSv1和SSlv3,此参数为创建SSLSocket
对象时默认的SSL/TLS协议版本。现在通过代理后,SSLSocket
对象中的enabledProtocols
已经变更成我们自定义的TLS_VERSION_LIST
,即同时包含了TLSv1、TLSv1.1、TLSv1.2协议版本。
另外,SSL/TLS协议版本中,还有一点需要注意的是,除了SSLSocket
对象支持的协议版本外,OkHttp还通过connectionSpecs
指定了一个连接规格
,连接规格
中,包含有tlsVersions
,此参数与SSLSocket
中的enabledProtocols
一起,用来控制实际连接建立时的终端SSL/TLS协议版本。
默认的取值逻辑如下:
static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(
ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT);
复制代码
其中,MODERN_TLS
对应的实现如下:
public static final ConnectionSpec MODERN_TLS = new Builder(true)
.cipherSuites(APPROVED_CIPHER_SUITES)
.tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
.supportsTlsExtensions(true)
.build();
复制代码
也就是说,默认情况下,ConnectionSpec
中的tlsVersions
对当前主流的SSL/TLS协议版本都是支持的。当然,特殊情况下,我们也可以人为去设置ConnectionSpec
并指定其内部的tlsVersions
。
下面我们看下tlsVersions
与enabledProtocols
取交集的具体逻辑。
private fun supportedSpec(sslSocket: SSLSocket, isFallback: Boolean): ConnectionSpec {
....
val tlsVersionsIntersection = if (tlsVersionsAsString != null) {
sslSocket.enabledProtocols.intersect(tlsVersionsAsString, naturalOrder())
} else {
sslSocket.enabledProtocols
}
....
return Builder(this)
.cipherSuites(*cipherSuitesIntersection)
.tlsVersions(*tlsVersionsIntersection)
.build()
}
/** Applies this spec to {@code sslSocket}. */
void apply(SSLSocket sslSocket, boolean isFallback) {
ConnectionSpec specToApply = supportedSpec(sslSocket, isFallback);
if (specToApply.tlsVersions != null) {
sslSocket.setEnabledProtocols(specToApply.tlsVersions);
}
if (specToApply.cipherSuites != null) {
sslSocket.setEnabledCipherSuites(specToApply.cipherSuites);
}
}
复制代码
也就是说,最终sslSocket
实例中的enabledProtocols
,除了基于TlsCompatSocketFactory
中对sslSocket
设置的enabledProtocols
外,最终还会和ConnectionSpec
内部的tlsVersions
取交集后,再次赋值给sslSocket
实例中的enabledProtocols
。
三、结语
总体上来说,Https通信时,SSL/TLS的协议版本,在客户端,首先取决于Android系统默认支持下的协议版本,并与ConnectionSpec
内部的tlsVersions
取交集,在服务端则依赖于服务端的配置。在握手阶段,客户端会和服务端协商最终的协议版本,取用两者同时支持的最高版本。
一般情况下,终端可以尽量放宽协议版本,这样当服务端更改协议版本,甚至只支持某一个协议版本(如TLSv1.1)时,在协议版本协商阶段,都是可以有尽量匹配的版本,从而对Https通信不造成影响。当然,Android 5.0以上的系统中,默认情况下,客户端对主流的协议版本都是支持的,一般不用做特殊处理。
end~
作者:HappyCorn
链接:https://juejin.im/post/5df8c7006fb9a01606716ba9
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Android 5.0以下系统支持TLS 1.1/1.2协议版本的更多相关文章
- android 4.0.4系统下实现apk的静默安装和启动
转 android 4.0.4系统下实现apk的静默安装和启动 分类: Android 2013-02-14 14:13 1762人阅读 评论(10) 收藏 举报 最近在android 4.0.4系统 ...
- Android 7.0 调取系统相机崩溃解决android.os.FileUriExposedException
一.写在前面 最近由于廖子尧忙于自己公司的事情和OkGo(一款专注于让网络请求更简单的网络框架) ,故让LZ 接替维护ImagePicker(一款支持单.多选.旋转和裁剪的图片选择器),也是处理了诸多 ...
- 【适配整理】Android 7.0 调取系统相机崩溃解决android.os.FileUriExposedException
一.写在前面 最近由于廖子尧忙于自己公司的事情和 OkGo (一款专注于让网络请求更简单的网络框架) ,故让LZ 接替维护 ImagePicker(一款支持单.多选.旋转和裁剪的图片选择器),也是处理 ...
- 【转】android 5.0 64bit系统加载库文件失败问题浅析
原文网址:http://blog.csdn.net/andrewblog/article/details/43601303 最近公司的一个项目使用android 5.0 64 bit平台,相对以前版本 ...
- 关于 Android 5.0 原生系统网络图标上的感叹号问题解决方法
解决方案 adb shell settings put global captive_portal_server g.cn 参考 关于 android 5.0 网络图标上的感叹号及其解决办法
- android 7.0 调用系统相机崩溃的解决方案(非谷歌官方推荐)
解决方案: 1.(推荐)7.0之后你的app就算有权限,给出一个URI之后手机也认为你没有权限. 不用修改原有代码,在Application的oncreate方法中:(或者直接放在调用相机的activ ...
- android 4.0 禁用系统home键
2.2 禁用系统home键.这里不说了. 近期项目有一个需求,禁用系统的全部键.像menu, home, back.同一时候还要是想点击响应与view的弹出. 就是UI这部分要正常. back键我们自 ...
- Android Support Font 安卓系统支持字体(配图)
测试了一台安卓机器,发现所有字体显示都一样.
- 使用 VirtualBox 虚拟机在电脑上运行 Android 4.0 系统,让电脑瞬间变安卓平板
Ref: http://www.iplaysoft.com/android-v4-ics-for-virtualbox.html 随着 Android 手机的各种软件应用越来越多,很多没有购买的朋友都 ...
随机推荐
- Java基础—实现多线程的三种方法
Java虚拟机(JVM,是运行所有Java程序的抽象计算机,是Java语言的运行环境)允许应用程序并发地运行多个线程.在Java语言中,多线程的实现一般有以下三种方法: 1.实现Runnable接口, ...
- ubuntu 18.04 修改Apache默认目录
ubuntu 18.04 修改Apache默认目录 安装是直接运行 sudu apt install apache2 安装之后要修改目录 vi /etc/apache2/sites-available ...
- gsoap生成webservice调用客户端接口
1.下载gsoap2.8 2.运行 wsdl2h.exe -o XXX.h XXX.wsdl wsdl文件可以是本地文件,也可以是服务器的wsdl,比如http://192.168.0.122:333 ...
- Vue计算属性computed的全面解析
前言 一直以来对computed这个计算属性都只停在一个大概的认知中,最近特意仔细研读相关资料,亲测后逐渐了解了其特性. 正文 computed 特点: 1.初始化/依赖属性(即data属性)改变时执 ...
- [b0010] windows 下 eclipse 开发 hdfs程序样例 (二)
目的: 学习windows 开发hadoop程序的配置 相关: [b0007] windows 下 eclipse 开发 hdfs程序样例 环境: 基于以下环境配置好后. [b0008] Window ...
- Django 简单评论实现
创建项目 django_comment 和应用 app01 修改 urls.py 文件 from django.contrib import admin from django.urls import ...
- mysql-5..6.23-win64.zip安装及配置
MySQL是一个小巧玲珑但功能强大的数据库,目前十分流行.但是官网给出的安装包有两种格式,一个是msi格式,一个是zip格式的.很多人下了zip格式的解压发现没有setup.exe,面对一堆文件一头雾 ...
- 阿里云Web应用防火墙采用规则引擎、语义分析和深度学习引擎相结合的方式防护Web攻击
深度学习引擎最佳实践 {#concept_1113021 .concept} 阿里云Web应用防火墙采用多种Web攻击检测引擎组合的方式为您的网站提供全面防护.Web应用防火墙采用规则引擎.语义分析和 ...
- Ubuntu下搭建Kubernetes集群(3)--k8s部署
1. 关闭swap并关闭防火墙 首先,我们需要先关闭swap和防火墙,否则在安装Kubernetes时会导致不成功: # 临时关闭 swapoff -a # 编辑/etc/fstab,注释掉包含swa ...
- JWT(Json Web Token):一种在Web应用中安全传递信息的规范 转载
文本将介绍一种在Web应用中安全传递信息的方式,称为JWT. 本文内容是对JWT官网介绍说明的英文翻译而来,由于本文英文水平有限,如有错误,还请指出,谢谢. What is JSON Web Toke ...