TLS/SSL证书是构建现代网络安全通信的基石,其核心依托于公钥与私钥组成的非对称加密体系。在TLS握手过程中,服务器利用私钥进行签名,以向客户端证明其对证书的所有权。为应对服务器频繁进行密钥轮替导致的客户端维护难题,业界引入了证书颁发机构(CA)这一信任中心。 CA通过其私钥为服务器证书签名,构建起一套稳固的信任链。客户端(如Android设备)只需预置受信任的根证书列表,即可校验成千上万个由这些机构颁发的服务器证书。Android系统(如Android8.0及以上版本)内置了上百个权威CA证书,确保了设备在访问不同Web服务时能够实现一致的安全验证。 在实际验证逻辑中,客户端不仅确认证书由合法的CA签发,还会严格比对证书中的主题名称或通配符域名,以防止身份冒充。技术人员通常利用OpenSSL等专业工具,通过命令行调用x509标准格式来提取并分析证书的颁发者与主题信息,从而排查各种连接安全问题。
本文介绍了与安全网络协议相关的最佳实践和公钥基础架构 (PKI) 注意事项。旨在指导开发者如何在Android应用中实现安全的网络通信,涵盖了从基础握手原理到高级安全配置的完整体系。
1.TLS/SSL核心概念
文章首先解释了服务器如何通过公钥和匹配的私钥在握手中证明身份。为了保证连接的长期稳定,Android推荐使用受信任的证书授权机构(CA)签发的证书。Android系统(如API 26及以上)预置了上百个CA根证书,确保了设备对主流网站的自动信任。
2.证书验证的常见问题与对策
当开发者遇到 SSLHandshakeException异常时,通常由以下三种情况引起:
3.技术实践与工具应用
HttpURLConnection等高级API,因为它们能自动处理复杂的验证逻辑。openssl命令行工具(如 s_client和 x509)查看服务器的证书链细节和主题信息。SSLSocket时,系统不会自动执行主机名验证,开发者必须手动调用 HostnameVerifier以防范中间人攻击。4.Android系统版本的演进与更新
Android10及以后:默认启用TLS 1.3协议,弃用不安全的SHA-1签名证书,并改进了 KeyChain证书选择机制。
吊销与屏蔽:Android具备远程更新CA拒绝名单的能力,以应对受损CA带来的安全风险。
安全测试:推荐使用Google开源工具 Nogotofail,针对网络流量进行自动化的漏洞和错误配置检测。

具有 TLS 证书的服务器拥有公钥和匹配的私钥。该服务器在 TLS 握手期间使用公钥加密为其证书签名。
简单的握手仅能证明服务器知道证书的私钥。为了解决此问题,请让客户端信任多个证书。如果指定服务器的证书未出现在客户端可信证书集中,则该服务器不可信。
但是,服务器可能会使用密钥轮替将证书的公钥更换为新的公钥。当服务器配置发生更改后,就需要更新客户端应用。如果服务器属于第三方网络服务(例如网络浏览器或电子邮件应用),则更难确定何时更新客户端应用。
服务器通常通过证书授权机构 (CA) 来颁发证书,这将确保客户端配置随着时间推移而更加稳定。CA 使用其私钥为服务器证书签名。然后,客户端可以检查服务器是否具有平台已知的 CA 证书。
可信 CA 通常列在主机平台上。Android 8.0(API 级别 26)包含 100 多个CA,这些 CA 在每个版本中都会更新,并且在不同设备之间保持一致。
客户端应用需要一种机制来验证服务器,因为 CA 为许多服务器提供证书。CA证书使用特定名称(如 gmail.com)或使用通配符(如 *.topssl.cn)来标识服务器。
如需查看网站的服务器证书信息,请使用 openssl 工具的 s_client 命令,传入端口号。默认情况下,HTTPS 使用端口 443。
该命令将 openssl s_client 的输出传输到 openssl x509,后者将根据 X.509 标准设置证书相关信息的格式。该命令会请求获取主题(服务器名称)和颁发者 (CA) 的信息。
openssl s_client -connect :443 | \
openssl x509 -noout -subject -issuer
假设您有一个由知名 CA 颁发证书的网络服务器,那么,您可以使用如下代码发起安全的请求:
KotlinJava
val url = URL("https://wikipedia.org")
val urlConnection: URLConnection = url.openConnection()
val inputStream: InputStream = urlConnection.getInputStream()
copyInputStreamToOutputStream(inputStream, System.out)
如需自定义 HTTP 请求,请转换为 <a href="https://developer.android.com/reference/java/net/HttpURLConnection?hl=zh-cn">HttpURLConnection</a>。Android HttpURLConnection 文档提供了有关处理请求和响应标头、发布内容、管理 Cookie、使用代理、缓存响应等的示例。Android 框架使用这些 API 验证证书和主机名。
请尽可能使用这些 API。以下部分介绍了一些常见问题,需要采用不同的解决方案。
假设 <a href="https://developer.android.com/reference/java/net/URLConnection?hl=zh-cn#getInputStream()">getInputStream()</a> 没有返回内容,而是抛出了异常:
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374)
at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)
出现这种情况的原因有很多,其中包括:
下面几部分将讨论如何解决这些问题,同时确保与服务器的连接安全无虞。
出现 <a href="https://developer.android.com/reference/javax/net/ssl/SSLHandshakeException?hl=zh-cn">SSLHandshakeException</a> 是因为系统不信任 CA。原因可能是您有一个由 Android 尚不信任的新 CA 颁发的证书,或您的应用在没有 CA 的较旧版本上运行。由于 CA 的私有性质,人们对其知之甚少。CA 未知的原因通常是它不是公共 CA,而是由政府、公司或教育机构等组织颁发的仅供其自己使用的私有 CA。
如需信任自定义 CA,而无需更改应用代码,请更改您的网络安全配置。
**注意:**很多网站都会介绍一种效果不佳的替代解决方案,即让您安装一个不起作用的 <a href="https://developer.android.com/reference/javax/net/ssl/TrustManager?hl=zh-cn">TrustManager</a>。如果这样做,您的用户在使用公共 Wi-Fi 热点时会很容易受到攻击,因为攻击者可以使用 DNS 骗术通过伪装成您的服务器的代理来发送您用户的流量。然后,攻击者可以记录密码和其他个人数据。攻击者之所以能得逞,是因为他们可以生成证书,并且如果没有可以切实验证相应证书是否来自受信任来源的 TrustManager,则无法阻止此类攻击。因此,请不要这样做,即使是暂时性的也不例外。而是将您的应用设置为信任服务器证书的颁发机构。
其次,由于自签名证书使得服务器成为自己的 CA,可能会出现 <a href="https://developer.android.com/reference/javax/net/ssl/SSLHandshakeException?hl=zh-cn">SSLHandshakeException</a>。这与证书授权机构未知的情况相似,因此请修改应用的“网络安全配置”以信任自签名证书。
第三,由于缺少中间 CA,会出现 <a href="https://developer.android.com/reference/javax/net/ssl/SSLHandshakeException?hl=zh-cn">SSLHandshakeException</a>。公共 CA 很少对服务器证书进行签名,而是由根 CA 对中间 CA 进行签名。
为了降低入侵风险,CA 将根 CA 保持离线状态。但是,Android 等操作系统通常仅直接信任根 CA,这会在服务器证书(由中间 CA 签名)与证书验证程序(识别根 CA)之间留下一个小的信任缺口。
为了消除此信任缺口,服务器在 TLS 握手期间会发送一系列证书,从服务器 CA 经由任何中间 CA 发送到可信根 CA。
例如,下面是通过 [openssl]s_client 命令看到的 mail.google.com 证书链:
$ openssl s_client -connect mail.google.com:443
---
Certificate chain
0 s:/C=US/ST=California/L=Mountain View/O=Google LLC/CN=mail.google.com
i:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
1 s:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
i:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
---
这表明服务器会为 mail.google.com 发送一个由 Thawte SGC CA(中间 CA)颁发的证书,为 Thawte SGC CA 发送另一个由 Verisign CA(Android 信任的主要 CA)颁发的证书。
但是,服务器可能未配置为包含必要的中间 CA。例如,下面的服务器会引发 Android 浏览器错误和 Android 应用异常:
$ openssl s_client -connect egov.uscis.gov:443
---
Certificate chain
0 s:/C=US/ST=District Of Columbia/L=Washington/O=U.S. Department of Homeland Security/OU=United States Citizenship and Immigration Services/OU=Terms of use at www.verisign.com/rpa (c)05/CN=egov.uscis.gov
i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 International Server CA - G3
---
与未知的 CA 或自签名服务器证书不同,大多数桌面浏览器在与此服务器通信时不会出现错误。桌面浏览器会缓存可信的中间 CA。当浏览器从某个网站了解中间 CA 后,就不会再需要在证书链中使用它。
有些网站会特意针对提供资源的辅助网络服务器执行此操作。为了节省带宽,它们会使用具有完整证书链的服务器提供主要的HTML页面,而用没有 CA 的服务器提供图片、CSS 和 JavaScript 等资源。这些服务器偶尔会提供您正尝试从 Android 应用访问的某种网络服务,但遗憾的是,它们无法获得应用信任。
如需解决此问题,请将服务器配置为在服务器链中加入中间 CA。大多数 CA 都可以提供有关如何为常用网络服务器执行此操作的说明。
到目前为止,所举示例都侧重于使用 <a href="https://developer.android.com/reference/javax/net/ssl/HttpsURLConnection?hl=zh-cn">HttpsURLConnection</a> 的 HTTPS。有时,应用需要单独使用 TLS 与 HTTPS。例如,某个电子邮件应用可能使用 TLS 的变体 SMTP、POP3 或 IMAP。在这些情况下,应用可以直接使用 <a href="https://developer.android.com/reference/javax/net/ssl/SSLSocket?hl=zh-cn">SSLSocket</a>,与 HttpsURLConnection 在内部执行的操作非常相似。
目前为止所介绍的用于处理证书验证问题的技术也适用于 SSLSocket。事实上,使用自定义 <a href="https://developer.android.com/reference/javax/net/ssl/TrustManager?hl=zh-cn">TrustManager</a> 时,传递到 HttpsURLConnection 的是 <a href="https://developer.android.com/reference/javax/net/ssl/SSLSocketFactory?hl=zh-cn">SSLSocketFactory</a>。因此,如果您需要结合使用自定义 TrustManager 和 SSLSocket,请遵循相同的步骤,并使用 SSLSocketFactory 创建您的 SSLSocket。
注意:SSLSocket不会执行主机名验证。由您的应用执行自己的主机名验证,最好通过使用预期的主机名调用 <a href="https://developer.android.com/reference/javax/net/ssl/HttpsURLConnection?hl=zh-cn#getDefaultHostnameVerifier()">getDefaultHostnameVerifier()</a> 进行验证。另请注意,出现错误时,<a href="https://developer.android.com/reference/javax/net/ssl/HostnameVerifier?hl=zh-cn#verify(java.lang.String,%20javax.net.SSL.SSLSession)">HostnameVerifier.verify()</a> 不会抛出异常,而是返回一个布尔值结果,您必须明确检查该结果。
TLS 依赖 CA 来仅向通过验证的服务器和网域所有者颁发证书。少数情况下,CA 也会受骗,或者像Comodo或 DigiNotar曾经遇到的那样遭到破坏,从而导致某个主机名的证书被颁发给除相应服务器或网域所有者以外的其他人。
为了降低此风险,Android 提供了将某些证书甚至整个 CA 列入拒绝名单的功能。尽管此名单过去已内置到操作系统中,但从 Android 4.2 开始,可以远程更新此名单,便于处理将来的泄露问题。
**注意:**不建议对 Android 应用使用证书固定的做法,即将被视为对应用有效的证书仅限于您之前授权的证书。未来如果对服务器配置进行更改(例如更改为其他 CA),会导致具有固定证书的应用无法连接到服务器,除非进行客户端软件更新。
如需将应用限制为仅接受您指定的证书,请务必添加多个备用 PIN 码(其中至少包括一个完全由您控制的密钥),并设置足够短的有效期以防止兼容性问题。网络安全配置中提供了这些固定功能。
本文重点介绍了如何使用 TLS 来确保与服务器之间的通信安全。TLS 也支持客户端证书的概念,客户端证书允许服务器验证客户端的身份。虽然这超出了本文的范围,但其中涉及的技术与指定自定义 TrustManager 类似。
在已知的 TLS/SSL 漏洞和错误配置方面,可以通过 Nogotofail 轻松确认您的应用是否安全。它是一款自动执行的工具,功能强大并且可扩展,用于测试通过它传送网络流量的任意设备的网络安全问题。
Nogotofail 可用于三个主要用例:
Nogotofail 适用于 Android、iOS、Linux、Windows、ChromeOS 和 macOS 操作系统。事实上,任何用于连接互联网的设备都可以使用 Nogotofail。Android 和 Linux 上提供了一个用于配置设置和获取通知的客户端,以及一个本身可作为路由器、VPN 服务器或代理部署的攻击引擎。
您可以在 Nogotofail 开源项目网站上访问此工具。
当 TLS 服务器在 TLS 握手中发送证书请求消息时,某些浏览器(如 Google Chrome)允许用户选择证书。从 Android 10 开始,KeyChain 对象会在调用 KeyChain.choosePrivateKeyAlias() 时信任颁发机构和密钥规范参数,以向用户显示证书选择提示。需要注意的是,此提示不包含不符合服务器规范的选项。
如果没有可用的用户可选证书(当没有与服务器规范匹配的证书或设备没有安装任何证书时,便会出现这种情况),则完全不会出现证书选择提示。
此外,在 Android 10 或更高版本上,无需具备设备屏幕锁定功能,就能将密钥或 CA 证书导入 KeyChain 对象中。
在 Android 10 及更高版本中,系统默认会为所有 TLS 连接启用 TLS 1.3。以下是有关 TLS 1.3 实现的一些重要的详细信息:
setEnabledCipherSuites() 停用该加密套件的操作均会被忽略。HandshakeCompletedListener 对象。(在 TLS 1.2 和之前的其他版本中,系统会在将会话添加到会话缓存之后调用这些对象。)SSLHandshakeException,而这些实例在 Android 10 及更高版本中会改为抛出 SSLProtocolException。如有需要,您可以通过调用 SSLContext.getInstance("TLSv1.2") 来获取已停用 TLS 1.3 的 SSLContext。您还可以对相关对象调用 setEnabledProtocols(),从而为每个连接启用或停用协议版本。
在 Android 10 中,使用 SHA-1 哈希算法的证书在 TLS 连接中不受信任。自 2016 年以来,根 CA 未再颁发过此类证书,因为它们不再受 Chrome 或其他主流浏览器的信任。
如果某网站使用的是 SHA-1 证书,则任何尝试连接该网站的操作都将失败。
当 TLS 服务器在 TLS 握手中发送证书请求消息时,某些浏览器(如 Google Chrome)允许用户选择证书。从 Android 10 开始,KeyChain 对象会在调用 KeyChain.choosePrivateKeyAlias() 时信任颁发机构和密钥规范参数,以向用户显示证书选择提示。需要注意的是,此提示不包含不符合服务器规范的选项。
如果没有可用的用户可选证书(当没有与服务器规范匹配的证书或设备没有安装任何证书时,便会出现这种情况),则完全不会出现证书选择提示。
此外,在 Android 10 或更高版本上,无需具备设备屏幕锁定功能,就能将密钥或 CA 证书导入 KeyChain 对象中。
Android 10 中引入的 TLS 和加密库方面的一些细小变更包括:
getOutputSize() 中返回更准确的缓冲区大小。如果在 Android 10 上运行的应用将 null 传递给 setSSLSocketFactory(),则会出现 IllegalArgumentException。在以前的版本中,将 null 传递给 setSSLSocketFactory() 与传入当前的默认工厂效果相同。
Android 的默认 SSLSocket 实现基于 Conscrypt。从 Android 11 开始,该实现是基于 Conscrypt 的 SSLEngine 在内部构建而成的。
本文原文地址:https://developer.android.com/privacy-and-security/security-ssl?utm_source=AboutSSL&utm_medium=Affiliate&utm_content=%2Fssl-certificate-errors-in-android-devices%2F
如何修复安卓设备中的SSL证书错误?
针对此类SSL证书错误,您必须在服务器上按照正确的逻辑顺序安装证书文件。为了确保Android设备能够顺利通过验证,完整的证书链条通常需要包含服务器证书(主证书)、中间证书以及根证书,请查看该文章了解!
加密您的网站,赢得客户信任!