最近,我们团队遇到了一个棘手的网络问题:手动更改 Android 设备的系统时间后,应用中的 HTTPS 请求开始大量失败,抛出 SSLHandshakeException。然而,最令人困惑的是,当我们将时间调回正常后,请求恢复正常;而通过 Charles 代理抓包时,请求也正常了;甚至,macOS 客户端在同样的时间修改下,访问也没有问题。
本文将深度剖析这一系列现象,揭示 时间偏差、证书弱点和不同平台安全策略 之间的联动机制。
现象一:时间修改导致请求失败,日志指向 SHA-1 弱点
当我们修改设备时间(例如,跳到未来的 2025 年)后,请求失败,日志中抛出以下关键信息:
Code snippet
javax.net.ssl.SSLHandshakeException: Unacceptable certificate: CN=DigiCert Global Root CA...
Caused by: java.security.cert.CertificateException: Signature uses an insecure hash function: 1.2.840.113549.1.1.5
这里的 1.2.840.113549.1.1.5 是 SHA-1 签名算法的 OID(对象标识符)。
核心分析:
表面上看,错误是由于服务器证书链中的某个证书使用了 不安全的 SHA-1 算法 而被 Android 拒绝。然而,如果服务器证书真的不安全,为什么在 使用默认系统时间时又是正常的呢?
这是因为,时间偏差是引爆器,SHA-1 弱点是炸弹本身。 当设备时间准确时,系统只需要检查证书的有效期。一旦时间被怀疑(手动修改),Android 的底层安全库 Conscrypt 会触发更严格的 链强度分析(ChainStrengthAnalyzer),并立即捕获并拒绝证书链中存在的 SHA-1 签名弱点,从而终止 SSL 握手。
现象二:Charles 代理下的请求恢复正常
当我们通过 Charles 或其他代理工具抓包时,即使设备时间仍然是错误的,失败的接口也立即恢复了正常访问。
核心分析:Charles 绕过了证书弱点。
Charles 代理是典型的 中间人(Man-in-the-Middle) 代理。它的工作机制是:
- Charles 接收到客户端的连接请求。
- Charles 动态地伪造一张新的证书,这张证书由安装在设备上的 Charles Root CA 签发。
- 最关键的是,Charles 伪造的证书使用的是 安全的哈希算法(通常是 SHA-256) ,因此它是一个“干净”的证书链。
- 当客户端对 Charles 伪造的证书进行校验时,由于它不含 SHA-1 弱点,即使设备时间不正确,也成功通过了 Android 严格的安全检查。
这一现象完美证实了:问题出在服务器提供的原始证书链上,而不是客户端应用逻辑或 Android 本身。
现象三:macOS 客户端不受影响
在同样的服务器和同样的时间修改下,macOS 版本的客户端访问完全正常,没有抛出异常。
核心分析:不同平台的安全策略差异。
macOS 和 Android 在处理 SSL/TLS 证书校验时,其底层安全框架(Android 的 Conscrypt vs. macOS 的 Secure Transport/URLSession)有着不同的优先级和容忍度:
- Android (严格): 采用 高敏感度策略。一旦检测到时间异常(有效期检查失败的信号),系统会立即跳转到最严格的安全检查,拒绝所有已知的弱点(如 SHA-1)。
- macOS (宽松): 采用 高容忍度策略。macOS 对时间错误的容忍度更高,或者其安全 API 不会在时间异常时,将 SHA-1 弱点视为致命的握手失败原因。它可能只会在日志中发出警告,但允许连接继续。
简而言之,Android 的安全策略更激进,它在“时间不稳定”时,率先发现了服务器证书的弱点并中断了连接。
结论与解决方案
通过这一系列现象的对比分析,我们确定了 Bug 的真相:
| 角色 | 问题 | 影响 |
|---|---|---|
| 服务器 | 证书链中存在 SHA-1 签名(弱点) | 问题的根本原因。 |
| Android 客户端 | 时间被修改(触发器) | 触发系统启动严格的 SHA-1 拒绝策略。 |
| Charles 代理 | 替换为安全的 SHA-256 证书(绕过) | 证明问题在于原始证书链的弱点。 |
根治方案 (服务器端必须):
我们必须通知运维团队,更新服务器的 SSL/TLS 证书链,确保所有证书(包括中间证书)都使用 SHA-256 或更强的哈希算法 签名。这是保证跨平台和未来安全兼容性的唯一途径。
客户端防御方案 (推荐):
在代码中捕获 SSLHandshakeException 时,进行精确判断,并引导用户解决触发问题:
- 捕获异常: 检查
onFailure中抛出的异常是否为javax.net.ssl.SSLHandshakeException。 - 提示校准时间: 提醒用户进入系统设置,开启 “自动确定日期和时间” ,确保系统时间准确。
放弃客户端的证书安全检查(如自定义 TrustManager 接受所有证书)是 不可接受的,因为这会使应用面临中间人攻击的巨大风险。
思考: 您的应用是否还有其他接口也使用了相同的弱点证书链?建议对所有网络请求进行彻底的安全审查,以避免在用户环境发生变化时再次触发此类问题。