{{item}}
{{item.title}}
{{items.productName}}
{{items.price}}/年
{{item.title}}
部警SSL证书可实现网站HTTPS加密保护及身份的可信认证,防止传输数据的泄露或算改,提高网站可信度和品牌形象,利于SEO排名,为企业带来更多访问量,这也是网络安全法及PCI合规性的必备要求
前往SSL证书开发者在集成HTTPS接口时会遇到各种SSL错误,其中因证书固定(Certificate Pinning) 配置不当导致的连接失败尤为常见且难以排查。这类错误通常表现为"SSL handshake failed"、"Certificate not trusted"或"SSL pinning mismatch",且往往在证书更新、服务器迁移或第三方网络环境下突然出现。本文将从技术原理出发,系统讲解证书固定的核心概念、常见错误原因、不同平台的实现差异,以及完整的适配与排查方案,帮助开发者彻底解决证书固定相关的SSL问题。
HTTPS通过SSL/TLS协议建立加密通道,其安全性依赖于证书信任链机制:客户端验证服务器证书是否由受信任的根证书颁发机构(CA)签发,以此确认服务器身份。
然而,传统的证书信任链存在两个致命弱点:
证书固定技术正是为了解决这些问题而诞生的。
证书固定(Certificate Pinning) 是一种增强型安全机制,它将服务器证书的特定信息(如公钥哈希、证书指纹)预先"硬编码"到客户端APP中。在SSL握手过程中,客户端不仅验证证书的合法性,还会额外检查服务器返回的证书是否与APP中预先存储的固定值匹配。
简单来说,证书固定相当于在客户端建立了一个"白名单",只有白名单中的证书才能被信任,即使系统信任库中存在其他恶意证书,也无法建立连接。
根据固定内容的不同,证书固定可分为以下两种:
| 固定类型 | 固定内容 | 优点 | 缺点 |
|---|---|---|---|
| 证书固定(Certificate Pinning) | 整个证书的哈希值 | 安全性最高,完全锁定特定证书 | 灵活性最差,证书更新时必须同步更新 APP |
| 公钥固定(Public Key Pinning) | 证书公钥的哈希值 | 灵活性较好,证书更新时只要公钥不变,APP 无需更新 | 安全性略低,若私钥泄露则完全失效 |
行业最佳实践:绝大多数APP采用公钥固定方式,因为它在安全性和可维护性之间取得了最佳平衡。
当APP启用证书固定后,任何导致服务器证书与固定值不匹配的情况都会触发SSL错误。以下是最常见的根本原因:
这是最常见的原因。当服务器证书到期更新时:
错误1:SSLHandshakeException: Certificate pinning failure
这是最典型的证书固定失败错误,完整错误信息通常如下:
javax.net.ssl.SSLHandshakeException: Certificate pinning failure!
Peer certificate chain:
sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=: CN=example.com, O=Example Corp, C=US
sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=: CN=Let's Encrypt Authority X3, O=Let's Encrypt, C=US
Pinned certificates for example.com:
sha256/CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=排查方法:
错误2:SSLHandshakeException: Trust anchor for certification path not found
这个错误可能由以下原因导致:
排查方法:
错误1:NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)
错误码-9802对应 errSSLServerAuthCompleted ,通常表示证书验证失败,包括证书固定不匹配。
错误2:Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid."
这个错误明确表示证书无效,可能的原因包括:
iOS特有排查技巧:
OkHttp是Android最常用的网络库,它内置了证书固定支持。以下是标准实现代码:
// 计算证书公钥的SHA-256哈希值
String certificatePins = "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(new CertificatePinner.Builder()
.add("example.com", certificatePins)
// 添加备用证书哈希值,用于证书更新过渡
.add("example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")
.build())
.build();关键注意事项:
这是最容易出错的步骤。以下是计算公钥SHA-256哈希值的正确方法:
方法1:使用OpenSSL命令行
# 从服务器获取证书并计算公钥哈希
openssl s_client -connect example.com:443 -servername example.com < /dev/null | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64方法2:使用OkHttp提供的工具
// 运行此代码获取正确的固定值
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build();
try {
// 尝试连接,会抛出异常并显示正确的哈希值
Request request = new Request.Builder().url("https://example.com").build();
client.newCall(request).execute();
} catch (SSLHandshakeException e) {
// 从异常信息中提取正确的哈希值
Log.e("CertificatePinner", e.getMessage());
}当服务器需要更新证书时,为了避免旧版本APP无法连接,必须采用以下过渡方案:
对于企业网络代理等需要绕过证书固定的场景,可以提供一个隐藏的调试选项,在特定条件下禁用证书固定:
OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (BuildConfig.DEBUG && isDebugModeEnabled()) {
// 调试模式下禁用证书固定
builder.certificatePinner(CertificatePinner.DEFAULT);
} else {
// 生产模式下启用证书固定
builder.certificatePinner(new CertificatePinner.Builder()
.add("example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build());
}重要警告:绝对不要在生产版本中提供禁用证书固定的公开选项,这会完全破坏证书固定的安全目的。
iOS平台的证书固定需要通过 NSURLSessionDelegate 协议的 URLSession:didReceiveChallenge:completionHandler: 方法实现:
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust else {
completionHandler(.performDefaultHandling, nil)
return
}
guard let serverTrust = challenge.protectionSpace.serverTrust else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
// 验证证书链
var error: CFError?
let isTrusted = SecTrustEvaluateWithError(serverTrust, &error)
guard isTrusted else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
// 执行证书固定检查
let pinnedHashes = Set(["AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="])
// 获取服务器证书链中的所有证书
let certificateCount = SecTrustGetCertificateCount(serverTrust)
var foundMatch = false
for i in 0..<certificateCount {
guard let certificate = SecTrustGetCertificateAtIndex(serverTrust, i) else {
continue
}
// 提取公钥
guard let publicKey = SecCertificateCopyKey(certificate) else {
continue
}
// 计算公钥的SHA-256哈希值
guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil) as Data? else {
continue
}
let hash = publicKeyData.sha256().base64EncodedString()
if pinnedHashes.contains(hash) {
foundMatch = true
break
}
}
if foundMatch {
completionHandler(.useCredential, URLCredential(trust: serverTrust))
} else {
completionHandler(.cancelAuthenticationChallenge, nil)
}
}推荐使用 TrustKit 这个成熟的第三方库,它提供了简单易用的证书固定API,并处理了许多边缘情况:
// 配置TrustKit
let trustKitConfig = [
kTSKSwizzleNetworkDelegates: true,
kTSKPinnedDomains: [
"example.com": [
kTSKPublicKeyHashes: [
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="
],
kTSKEnforcePinning: true,
kTSKIncludeSubdomains: true
]
]
] as [String: Any]
TrustKit.initSharedInstance(withConfiguration: trustKitConfig)从iOS 14开始,苹果引入了新的ATS配置选项,可以在Info.plist中直接配置证书固定,无需编写代码:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSPinnedDomains</key>
<dict>
<key>example.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSPinnedLeafIdentities</key>
<array>
<dict>
<key>NSPublicKeyDigest</key>
<data>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</data>
<key>NSPublicKeyDigestAlgorithm</key>
<string>sha256</string>
</dict>
</array>
</dict>
</dict>
</dict>注意:这种方式虽然简单,但灵活性较差,无法实现动态更新固定值。
虽然证书固定是一种有效的安全机制,但它也存在维护成本高、证书更新复杂等缺点。以下是一些替代方案:
证书透明度是一种由谷歌推动的安全标准,它要求所有CA签发的证书都必须记录在公开的日志中。客户端可以通过查询这些日志来验证证书的合法性,从而防止恶意签发的证书。
iOS 12.1+和Android 7.0+已内置对证书透明度的支持。
动态证书固定允许APP从服务器动态获取最新的固定值,而无需发布新版本。这种方式结合了证书固定的安全性和动态更新的灵活性。
实现方式:
许多第三方安全服务提供商(如Google Play App Signing、Apple App Attest)提供了内置的证书固定和安全通信功能,可以大大降低开发者的维护成本。
证书固定是移动端APP保护HTTPS通信安全的重要手段,但如果配置不当,会导致严重的SSL连接错误。本文系统讲解了证书固定的技术原理、常见错误原因、不同平台的实现方式,以及完整的适配与排查方案。
Dogssl.cn拥有20年网络安全服务经验,提供构涵盖国际CA机构Sectigo、Digicert、GeoTrust、GlobalSign,以及国内CA机构CFCA、沃通、vTrus、上海CA等数十个SSL证书品牌。全程技术支持及免费部署服务,如您有SSL证书需求,欢迎联系!