18. TLS 1.3 新特性:Android 中的支持与配置

聊到 TLS 1.3,我其实挺感慨的。这个协议从 2018 年正式发布,到现在已经好几年了。但说实话,我在很多 Android 项目里检查代码时,发现不少人还在用 TLS 1.2,甚至更老的版本。嗯,这其实是个隐患。

TLS 1.3 不是简单的升级,它是一次彻底的协议重构。说白了,它把很多历史包袱都扔掉了。我当年第一次看 RFC 8446 时,第一反应是:「终于有人把那些陈年旧伤给治了。」

18.1 TLS 1.3 到底改了啥?

先说说最直观的变化——握手速度。TLS 1.2 完成一次完整握手需要 2-RTT(两次往返),而 TLS 1.3 只需要 1-RTT。如果你之前已经连过这个服务器,甚至能做到 0-RTT。我在一个海外项目里实测过,从上海连到美西的服务器,TLS 1.3 比 1.2 快了将近 200 毫秒。对于用户体验来说,这差别很明显。

为什么会这么快?因为 TLS 1.3 把密钥协商和证书验证合并到了一次往返里。它去掉了那些可选的、拖慢速度的步骤。你想想看,以前要来回确认好几轮,现在一轮搞定,效率自然上去了。

另一个重大变化是——它砍掉了一堆不安全的加密套件。我记得 TLS 1.2 时代,光是加密套件就有几十种,很多还是带坑的。比如 CBC 模式的密码,容易受到 padding oracle 攻击。TLS 1.3 直接把这些全删了,只保留了 AEAD 类算法(比如 AES-GCM、ChaCha20-Poly1305)。

核心变化一览:

  • 握手从 2-RTT 降到 1-RTT(0-RTT 可选)
  • 移除了所有不安全/过时的加密套件
  • 强制使用前向安全(ECDHE 或 DHE)
  • 简化了密钥派生流程
  • 废弃了静态 RSA 和 DH 密钥交换

18.2 Android 对 TLS 1.3 的支持情况

这个问题我经常被问到。直接说结论:Android 10(API 29)及以上原生支持 TLS 1.3。底层用的是 Conscrypt 安全提供者,它从 Android 10 开始默认启用了 TLS 1.3。

但这里有个坑——很多国产手机厂商会魔改系统。我曾经在一台某品牌的 Android 11 手机上发现,它的 Conscrypt 版本被降级了,导致 TLS 1.3 无法正常工作。嗯,这种问题排查起来很头疼。

Android 版本 API 级别 TLS 1.3 支持 默认启用
Android 9 及以下 ≤ 28 不支持(需第三方库)
Android 10 29 支持
Android 11 30 支持
Android 12+ 31+ 支持(增强)

如果你需要兼容 Android 9 及以下设备,我建议用 Google Play 服务提供的 Provider Installer,或者直接集成 Conscrypt 的独立 jar 包。不过说实话,现在 Android 9 以下的设备占比已经很低了,我个人倾向于直接要求最低 API 29。

18.3 如何在代码中配置 TLS 1.3

配置其实很简单。如果你用的是 HttpsURLConnection,默认情况下 Android 10+ 会自动协商 TLS 1.3。但如果你想显式指定,可以这样做:

// 显式指定 TLS 1.3
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(null, null, null);

URL url = new URL("https://api.example.com");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(sslContext.getSocketFactory());

如果你用的是 OkHttp,那就更简单了。OkHttp 3.12+ 已经支持 TLS 1.3,而且它会自动协商最优协议版本。我个人习惯这样配置:

OkHttpClient client = new OkHttpClient.Builder()
    .connectionSpecs(Arrays.asList(
        ConnectionSpec.RESTRICTED_TLS,  // 只允许 TLS 1.2 和 1.3
        ConnectionSpec.COMPATIBLE_TLS   // 兼容模式
    ))
    .build();

小提示:ConnectionSpec.RESTRICTED_TLS 只允许 TLS 1.2 和 1.3,并且强制使用前向安全的加密套件。如果你的服务器配置没问题,我建议直接用这个。

18.4 0-RTT 模式:快,但有代价

TLS 1.3 的 0-RTT 模式确实诱人——你可以在第一次请求时就带上数据,省掉一次往返。但我曾经在项目中吃过它的亏。

0-RTT 存在重放攻击的风险。什么意思呢?攻击者可以截获你的 0-RTT 数据包,然后重复发送给服务器。如果服务器没有做幂等性处理,就可能造成重复下单、重复扣款等问题。

所以我的建议是:不要在写操作(POST、PUT、DELETE)中使用 0-RTT。只用于读操作,比如 GET 请求。或者,确保你的服务器端实现了重放检测机制。

警告:Android 的 Conscrypt 实现默认禁用了 0-RTT。如果你非要开启,需要手动配置 SSLSession 的 PSK(预共享密钥)。但除非你非常清楚自己在做什么,否则别碰它。

18.5 加密套件的变化

TLS 1.3 只保留了 5 个加密套件。你没看错,就 5 个。相比 TLS 1.2 的几十个,这简直是断舍离的典范。

套件名称 密钥交换 认证 加密 Android 支持
TLS_AES_128_GCM_SHA256 ECDHE RSA/ECDSA AES-128-GCM
TLS_AES_256_GCM_SHA384 ECDHE RSA/ECDSA AES-256-GCM
TLS_CHACHA20_POLY1305_SHA256 ECDHE RSA/ECDSA ChaCha20-Poly1305
TLS_AES_128_CCM_SHA256 ECDHE RSA/ECDSA AES-128-CCM ⚠️ 部分支持
TLS_AES_128_CCM_8_SHA256 ECDHE RSA/ECDSA AES-128-CCM-8 ❌ 不支持

我个人推荐优先使用 TLS_AES_128_GCM_SHA256TLS_CHACHA20_POLY1305_SHA256。前者在大多数硬件上有 AES 指令集加速,后者在低端设备上表现更好。我在一个 IoT 项目里测试过,ChaCha20 在 ARM Cortex-A 系列芯片上比 AES 快 30% 左右。

18.6 服务端兼容性检查

配置好客户端只是第一步。你还需要确认服务端是否支持 TLS 1.3。我曾经遇到过一个案例:客户端明明配好了,但连不上服务器。查了半天,发现是 Nginx 版本太老,不支持 TLS 1.3。

你可以用 OpenSSL 命令行快速检查:

openssl s_client -connect api.example.com:443 -tls1_3

如果返回了证书链和握手信息,说明服务端支持。如果报错,那就得升级服务端了。Nginx 1.14+、Apache 2.4.37+、Tomcat 9.0.13+ 都支持 TLS 1.3。

避坑指南:我曾经在项目中遇到一个奇葩问题——服务端宣称支持 TLS 1.3,但实际只支持了部分加密套件。客户端协商时选了服务端不支持的套件,结果握手失败。解决方案是:在客户端显式指定加密套件列表,不要依赖自动协商。

18.7 知识体系总览

下面这张图总结了 TLS 1.3 在 Android 中的核心要点。你可以把它当作一个快速参考。

TLS 1.3 在 Android 中的知识体系 TLS 1.3 核心新特性 • 1-RTT 握手 • 0-RTT 可选 Android 支持 • API 29+ 原生支持 • 低版本需第三方库 配置方法 • SSLContext 显式指定 • OkHttp 自动协商 加密套件(5个) • AES-128/256-GCM • ChaCha20-Poly1305 0-RTT 风险 • 重放攻击 • 仅用于读操作 服务端兼容性 • OpenSSL 检查 • Nginx/Apache 版本 建议:API 29+ 优先使用,低版本升级或集成 Conscrypt

18.8 写在最后

TLS 1.3 不是银弹,但它确实解决了很多 TLS 1.2 时代的顽疾。我个人建议,新项目直接上 TLS 1.3,老项目逐步迁移。迁移过程中注意两点:一是服务端兼容性,二是 0-RTT 的安全风险。

嗯,如果你在配置过程中遇到奇怪的问题,不妨先抓个包看看。Wireshark 里过滤 tls.handshake.type == 1 就能看到 ClientHello,确认一下协商的版本和套件。这招我用了很多年,屡试不爽。


公众号:蓝海资料掘金营,微信deep3321