Android 17 + OkHttp 5.5.0 ,全新 ECH 下你的 HTTPS 域名可以请求时被安全隐藏

在 Android 17 上目前官方主要增加了几个网络安全能力:Encrypted Client Hello、局域网权限隔离、默认证书透明度检查,这里面我感觉最有意思的是 Android 17 与 OkHttp 5.5.0 一起完成了 ECH 的落地。

这个的作用主要是补上了 HTTPS 一直以来存在的一个隐私缺口,过去 HTTPS 可以保护消息内容、账号密码、URL 路径和请求参数之类,但是隐藏不了用户正在访问哪个域名,而 Android 17 现在提供了系统级 TLS 与 DNS 接口,OkHttp 5.5.0 支持讲这些能力接入到 App 里面。

HTTPS 藏不聊域名,主要是因为当用户访问 https://bank.example.com/account/transactions 的时候,传统 HTTPS 会加密 /account/transactions、Cookie、请求正文和服务器返回内容,但 bank.example.com 对外部没做任何隐藏。

这里域名主要会从两个地方泄露:

  • 第一个是 DNS 查询,设备需要先把 bank.example.com 解析成 IP 地址,如果使用普通 DNS,查询会内容直接暴露在路由上,Android 的 Private DNS、DNS over TLS 和 DNS over HTTPS 可以处理这一层
  • 第二个是 TLS 握手中的 SNI,现在大量网站共用 CDN 和 IP 地址,服务器需要在握手初期知道客户端要访问哪个域名,才能选择正确的证书和后端,这个域名长期以明文形式出现在 ClientHello 中,也就是即便 DNS 已经加密,运营商依然可以从 SNI 恢复访问目标

而 ECH 就是对第二个泄露点下手,2026 年 3 月正式成为 RFC 9849 后,ECH 就可以将真实 SNI、ALPN 这些敏感握手字段放入加密的 ClientHelloInner,外部只保留一个面向 CDN 或 ECH 服务商的 ClientHelloOuter链路上的观察者还是可以看到目标 IP、连接时间和流量大小,但是没办法从 TLS 握手直接读出真实域名

它的隐私效果在大型 CDN 上特别明显,比如一个 IP 可能同时托管数千个域名,观察者只能判断设备正在连接 Cloudflare、Fastly 或其他服务商,但是没办法直观确认背后具体是哪一个网站。

所以核心就会 ECH 会加密了 TLS 握手中暴露真实域名的 SNI 等字段,配合加密 DNS,能同时封住 DNS 查询和 TLS ClientHello 这两个主要出口

而实际对应 Android 17 提供了两类关键接口:

  • DnsResolver 可以查询携带 ECH 配置的 HTTPS DNS Resource Record
  • ConscryptSSLSocketSSLEngine 可以接收这份配置并执行 ECH 握手

开发者还可以通过 Network Security Configuration 中的 <domainEncryption> 控制全局或单域名行为。

所以就算操作系统具备 ECH 能力,也还需要 App 使用的网络库主动接入,Android App 的 HTTPS 请求可能来自 OkHttp、Cronet、WebView、自研 C++ 网络栈或其他运行时,所以系统无法自动改写所有网络库的 DNS 和 TLS 流程。

这里 OkHttp 5.5.0 的意义就在这里,它承担了从发现 ECH 配置、选择连接地址到把配置交给 Android TLS 栈的全部工作:

阶段 OkHttp 5.5.0 的工作 Android 17 的工作
DNS 查询 并行查询 A、AAAA 和 HTTPS RR DnsResolver 提供原始 HTTPS RR 查询能力
读取配置 从 HTTPS RR 解析 echConfigList、ALPN、端口和 IP Hint 通过 Network Security Policy 判断该域名是否允许 ECH
选择连接 优先保留携带 ECH 配置的路由,避免无意回退到明文 SNI 提供 IP、网络绑定和 TLS 能力
TLS 握手 ECHConfigList 交给 Socket Adapter SSLSockets.setEchConfigList() 执行 ECH
服务端不支持 发出 ECH GREASE,让不同连接具有相似外观 TLS 栈生成相应握手
配置过期 验证公开名称和服务端返回的新配置,再进行一次安全重试 Conscrypt 返回 ECH 拒绝及重试信息

所以一条完整请求大致会经历这样的过程:

  • OkHttp 首先通过 DNS 获取 IP 地址,同时查询 HTTPS RR
  • HTTPS RR 中包含服务端的 ECH 公钥和相关元数据
  • OkHttp 把 ECHConfigList 交给 Android 17 的 TLS Socket
  • TLS 栈生成外层和内层两个 ClientHello
  • CDN 使用私钥解开内层握手,随后按照真实域名完成证书选择与连接路由

这里还有一个有意思的细节,那就是 OkHttp 5.5.0 的路由选择代码会检查 DNS 返回结果,只要存在携带 ECH 配置的可用路由,就排除同一批结果中没有 ECH 的路由。

这可以降低攻击者或错误配置诱导客户端静默降级到明文 SNI 的风险,服务端 ECH 密钥轮换后,如果客户端拿到过期配置,OkHttp 也会验证外层公开域名的证书,再接受服务端给出的新配置并重试,而不会对任意网络错误直接关闭 ECH。

所以 OkHttp 5.5.0 最大的改造其实发生在 DNS 层:

  • 旧版 OkHttp 的 Dns.lookup(hostname) 主要返回一组 IP 地址,适合查询 A 和 AAAA 记录,但是没有地方承载 HTTPS RR 中的服务元数据,而 ECH 公钥恰好发布在 HTTPS RR 的 ech 参数里,原有 API 很难接入

  • OkHttp 5.5.0 重新设计了 DNS API。新的 Dns.newCall() 可以异步返回多种记录,结果里除了 IpAddress,还增加了 ServiceMetadata,其中包含:

    • ECH 配置 echConfigList
    • 服务端支持的 ALPN,例如 HTTP/2、HTTP/3
    • 可覆盖的连接端口
    • IPv4、IPv6 地址提示
    • 替代服务主机信息

这样 A、AAAA 和 HTTPS RR 查询会并行进行,然后通过新的内存缓存复用结果.

Jigsaw 对全球热门一万个域名的测量显示,超过 93% 的 HTTPS RR 和普通地址记录返回时间相差不超过约 50 毫秒,3.1% 的域名差距超过 100 毫秒,另有几十个域名始终没有正常返回 HTTPS RR。

另外,60% 的 HTTPS RR 能在 TCP 连接开始前返回,如果其中带有 IP Hint 或 HTTP/3 信息,客户端甚至有机会节省后续查询或握手时间。

这也解释了 OkHttp 为什么加入并行查询、异步流式结果和缓存,ECH 会增加一种 DNS 记录查询,但这部分开销通常可以与 A、AAAA 查询重叠。

不过就算升级到 OkHttp 5.5.0 以后,ECH也需要显式开启,比如

scss 复制代码
val bootstrapClient = OkHttpClient()
​
val dnsOverHttps = DnsOverHttps.Builder()
    .client(bootstrapClient)
    .url("https://1.1.1.1/dns-query".toHttpUrl())
    .build()
​
val client = bootstrapClient.newBuilder()
    .dns(dnsOverHttps)
    .build()

对应依赖是:

scss 复制代码
implementation("com.squareup.okhttp3:okhttp:5.5.0")
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:5.5.0")

DoH 会同时加密 DNS 查询,同时默认读取 ECH 所需的 HTTPS RR,这样就可以覆盖完整的域名隐私链条:

DNS 查询不再明文暴露域名,TLS ClientHello 也不再明文发送真实 SNI。

另一种方式是 Android 17 的系统解析器:

scss 复制代码
val client = OkHttpClient.Builder()
    .dns(AndroidDns())
    .build()

AndroidDns 会通过系统解析器获取 IP,并额外查询 HTTPS RR,这种更容易融入系统网络、VPN、按网络绑定和企业配置,但 DNS 查询是否加密取决于设备的 Private DNS 和当前网络环境。

OkHttp 的 Changelog 也明确提醒:使用 AndroidDns 虽然可以启用 ECH,但是默认 DNS 路径不一定会经过加密,所以域名还可能在最初的 DNS 查询中泄露。

Android 17 对面向 API 37 的 App 默认使用 domainEncryption mode="enabled",这个模式下 DNS 提供 ECH 配置时强制使用 ECH,没有配置时发送 ECH GREASE,这里开发者可以继续通过 network_security_config.xml 明确声明或按域名关闭:

xml 复制代码
<network-security-config>
    <base-config>
        <domainEncryption mode="enabled" />
    </base-config>
</network-security-config>

所以实际部署至少要同时满足几个条件:

  • 设备运行 Android 17
  • App 使用支持 ECH 的 OkHttp 5.5.0
  • DNS 能返回 HTTPS RR、服务端或 CDN 已经部署 ECH

不过有一个更大的问题是, OKHttp 不支持 KMP ,甚至不打算支持 KMP ,说这个是最大的痛点。

相关推荐
小强闯江湖2 小时前
不只是让 AI 写代码:ViewCompose 把 Android UI 做成了可编译、可渲染的闭环
android·人工智能·kotlin
后除3 小时前
从零到一: 创建一个 TypeScript 7 项目
前端·webpack·typescript
lerhxx3 小时前
我用 R3F 手搓了一个能走进去的 3D 迷宫简历(上):从选型架构到迷宫生成算法
前端·javascript·three.js
还有多久拿退休金3 小时前
不调多模态,纯文本大模型如何给系统操作配上截图
前端·llm·aigc
hunterandroid4 小时前
HarmonyOS WebSocket 实战:断线重连、心跳保活与连接状态机设计
前端
hunterandroid4 小时前
StateFlow 与 SharedFlow 的边界:状态与事件的正确建模
android·前端
YF02114 小时前
Android遥控器对频详解
android·android things
心念科技4 小时前
1、搜索表单 xnSearch(基于:心念后台,后端 Java 21 + Spring Boot 4 + Spring Cloud,前端提供 ReactVue3+TS、Vue3+JS、Vue2+JS
前端
计算机魔术师5 小时前
Dwarkesh Patel 对 OpenAI/Hugging Face 事件的爆款解读被指危险误导
前端