
在 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 RecordConscrypt、SSLSocket和SSLEngine可以接收这份配置并执行 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 RRDnsResolver提供原始 HTTPS RR 查询能力读取配置从 HTTPS RR 解析echConfigList、ALPN、端口和 IP Hint通过 Network Security Policy 判断该域名是否允许 ECH选择连接优先保留携带 ECH 配置的路由避免无意回退到明文 SNI提供 IP、网络绑定和 TLS 能力TLS 握手将ECHConfigList交给 Socket AdapterSSLSockets.setEchConfigList()执行 ECH服务端不支持发出 ECH GREASE让不同连接具有相似外观TLS 栈生成相应握手配置过期验证公开名称和服务端返回的新配置再进行一次安全重试Conscrypt 返回 ECH 拒绝及重试信息所以一条完整请求大致会经历这样的过程OkHttp 首先通过 DNS 获取 IP 地址同时查询 HTTPS RRHTTPS RR 中包含服务端的 ECH 公钥和相关元数据OkHttp 把ECHConfigList交给 Android 17 的 TLS SocketTLS 栈生成外层和内层两个 ClientHelloCDN 使用私钥解开内层握手随后按照真实域名完成证书选择与连接路由这里还有一个有意思的细节那就是 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也需要显式开启比如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()对应依赖是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 的系统解析器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 modeenabled这个模式下 DNS 提供 ECH 配置时强制使用 ECH没有配置时发送 ECH GREASE这里开发者可以继续通过network_security_config.xml明确声明或按域名关闭network-security-config base-config domainEncryption modeenabled / /base-config /network-security-config所以实际部署至少要同时满足几个条件设备运行 Android 17App 使用支持 ECH 的 OkHttp 5.5.0DNS 能返回 HTTPS RR、服务端或 CDN 已经部署 ECH不过有一个更大的问题是 OKHttp 不支持 KMP 甚至不打算支持 KMP 说这个是最大的痛点。