Certificate Pinning:它是什么、机制和固定方法

作者: IT Sectr 发布日期: 2026-03-09 阅读时间: 8 分钟

Certificate Pinning — 一种固定服务器证书或公钥的机制,应用程序使用预先知道的指纹来验证HTTPS连接。与通过CA的标准信任链不同,pinning保证即使受损的证书颁发机构也无法为您的域名颁发伪造证书。根据OWASP MSTG (2025),Certificate Pinning列入L2保护级别应用的强制控制列表。实施包括在代码中存储证书哈希值并在每次请求时进行验证。

要点

  • Certificate Pinning — 一种技术,应用程序只信任具有预先知道指纹的证书,忽略整个CA链
  • Public Key Pinning — 替代方案,仅固定公钥,简化证书更换时的轮换
  • HPKP (HTTP Public Key Pinning) — 过时的HTTP标头级别标准,不推荐用于新项目
  • Backup pins — 备用指纹,在主证书更换或过期时确保连接连续性
  • 实施 在iOS上通过SecTrustEvaluate,在Android上通过OkHttp中的CertificatePinner或TrustManager

什么是Certificate Pinning?

Certificate Pinning — 是一种安全技术,应用程序存储可信证书的指纹(fingerprint),并将其作为建立HTTPS连接的唯一标准。在标准TLS模型中,客户端检查服务器证书是否由可信根CA签名 — 系统中预装的数百个机构之一。Certificate Pinning用直接检查取代了这个链:证书必须与存储的样本匹配或包含预期的公钥。

标准模型的问题在CA受损事件后变得明显 — DigiNotar(2011)、Comodo(2011)、TrustCor(2022)。如果CA为您的域颁发伪造证书,浏览器或应用程序会将其视为有效。Certificate Pinning 可防止此攻击:即使完美签名的伪造证书也会被拒绝,因为其指纹与应用程序中固定的指纹不匹配。

术语pinning源自pin — “别针”或“固定器”:开发者固定可信证书,任何偏离都会阻止连接。根据Mitre CWE-295的研究,不正确的证书验证仍然是移动应用中最危险的十大安全错误之一,而Certificate Pinning是直接预防方法。

Certificate Pinning的历史和演变

最初,Certificate Pinning通过HPKP(HTTP Public Key Pinning)机制在浏览器中使用,在RFC 7469中标准化。开发者发送带有预期密钥哈希的HTTP标头Public-Key-Pins,浏览器在指定时间段内记住它们。然而,HPKP被证明是危险的:一个配置错误可能使网站被封锁数月。2018年,Chrome停止了对HPKP的支持,现在标准是软件实施在客户端 — 在移动应用程序或浏览器扩展内。

Certificate Pinning如何工作?

Certificate Pinning过程包括三个关键阶段:计算指纹、连接时验证和错误处理。在准备阶段,开发者获取生产服务器证书或公钥的SHA-256哈希。对于符合GDPR和PCI DSS的应用程序,还需要固定链中中间CA的指纹。

在每个HTTPS请求中,应用程序拦截TLS身份验证回调,提取服务器证书并计算其SHA-256哈希。此哈希与存储的可信指纹列表进行比较。如果找到匹配 — 连接继续。如果没有 — 应用程序必须断开连接并报告错误,而不向攻击者透露实施细节。

kotlin
fun validateCertificate(certificate: X509Certificate,
    expectedHash: String): Boolean {
    val digest = MessageDigest.getInstance("SHA-256")
    val hash = Base64.encodeToString(
        digest.digest(certificate.publicKey.getEncoded()),
        Base64.DEFAULT
    ).trim()
    return hash == expectedHash
}

该函数接收来自服务器的X509Certificate对象和预期哈希。首先提取证书的公钥,计算SHA-256哈希并编码为Base64。结果与预期指纹进行比较。在生产环境中,建议添加对2–3个指纹数组的检查以支持轮换。

Certificate Pinning vs Public Key Pinning

在实施pinning时,需要选择固定哪个加密对象。Certificate Pinning绑定到X.509证书本身 — 其序列号、有效期和整个链。Public Key Pinning仅固定证书内的公钥,忽略其他字段。选择显著影响运营成本。

标准Certificate PinningPublic Key Pinning
固定对象整个X.509证书RSA/ECDSA公钥
轮换每次重新颁发时都需要更新使用相同密钥更换证书时不变
安全性最精确的绑定对细节不那么敏感
灵活性低 — 证书每1–2年更换高 — 密钥可以使用5–10年
建议用于具有受控更新的关键系统用于大多数移动应用程序和API

Public Key Pinning — 大多数项目的首选选择。服务器的公钥通常在证书重新颁发时保持不变 — 公司只需用新证书签署旧密钥。这意味着如果密钥对没有更改,应用程序在证书更改后不需要更新。Certificate Pinning推荐用于开发者完全控制服务器和客户端代码的场景,例如具有严格更新周期的企业应用程序。

Trust On First Use(TOFU)

TOFU — 一种策略,Certificate Pinning不会预先配置,而是在首次连接到服务器时记住证书。这种方法适用于预先不知道要连接哪个服务器的应用程序。缺点 — 对首次攻击的脆弱性:如果首次连接被拦截,伪造证书将被视为可信。TOFU用于SSH连接和一些P2P协议。

在iOS和Android上的实施

在两个平台上,Certificate Pinning通过在网络堆栈级别拦截TLS连接来实现。在iOS上,使用URLSession委托或Alamofire ServerTrustManager。在Android上,首选方法是OkHttp CertificatePinner,它内置于流行的HTTP客户端中,并支持为每个域配置多个指纹。

swift
func validate(serverTrust: SecTrust,
    pinnedHash: String) -> Bool {
    guard let certificates = SecTrustCopyCertificateChain(serverTrust)
        as? [SecCertificate] else { return false }

    for certificate in certificates {
        let data = SecCertificateCopyData(certificate)
        var hash = Data(repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
        data.withUnsafeBytes {
            CC_SHA256($0.baseAddress,
                CC_LONG(data.count), &hash)
        }
        if hash.base64EncodedString() == pinnedHash {
            return true
        }
    }
    return false
}

在Swift函数中,从serverTrust提取证书链,为每个证书计算SHA-256哈希,并将结果与预期值进行比较。遍历链中的所有证书可以在中间CA级别实现pinning — 如果中间证书匹配,连接被接受。这为叶证书轮换提供了灵活性。

Android的TrustManager(自定义)

如果应用程序不使用OkHttp,可以通过自定义X509TrustManager实现Certificate Pinning。此方法需要更多代码,但提供对验证过程的完全控制。TrustManager覆盖checkServerTrusted方法,开发者在此手动检查服务器证书并做出信任决定。仅在OkHttp库不可用的特定场景下推荐。

实施Certificate Pinning时的错误

最常见的错误 — 缺乏备用pin。开发者放置一个证书指纹,当其过期时,用户集体失去连接。最低可接受配置 — 两个指纹:当前证书和备用。最优 — 三个:当前、备用和根CA指纹作为后备。

第二个错误 — 在代码中以开放形式存储pin。有权访问APK或IPA的攻击者可以轻松提取和替换指纹。建议对哈希进行混淆:将字符串分成部分,存储在加密资源中或通过运行时计算。对于Android,具有字符串常量混淆的ProGuard是有效的。

第三个错误 — 在开发证书级别进行pinning。开发和生产的证书通常不同,但开发者在构建release时经常忘记切换pin。结果 — 生产应用程序无法连接到服务器。解决方案 — 通过BuildConfig或特定风味的资源为debug和release设置单独的pin配置。

  • Ignoring certificate chain — 仅检查叶证书而不考虑中间CA,导致轮换时连接中断
  • Hardcoded dates — 硬编码的证书过期日期,更新后不会改变
  • No monitoring — 缺少Certificate Pinning错误的警报,导致问题只能从用户处发现
  • TOFU无验证 — 使用Trust On First Use而不加额外检查,允许首次MITM攻击固定伪造证书

常见问题

Certificate Pinning与SSL Pinning有何不同?

SSL Pinning — 绑定到SSL/TLS证书的通用术语。Certificate Pinning — 固定X.509证书本身的特定实现,而不仅仅是公钥。区别在于固定对象:证书vs密钥。

如何在应用程序中安全存储证书指纹?

建议通过ProGuard(Android)混淆或通过Keychain(iOS)加密将哈希存储在资源中。避免在未加密的strings.xml或Info.plist中以开放形式存储pin。

固定指纹需要多久更换一次?

服务器证书每次更改时。建议在当前证书过期前3–6个月添加新指纹作为备用pin,轮换后删除旧指纹。至少需要一个备用pin。

可以禁用Certificate Pinning进行调试吗?

可以,通过条件编译:在debug构建中pinning被禁用,在release中启用。使用Android上的BuildConfig.DEBUG或iOS上的#if DEBUG进行切换。切勿通过用户可访问的运行时标志进行操作。

如果证书被损坏该怎么办?

立即发布带有新指纹的应用程序更新并在商店发布。使用强制更新机制。如果备用pin包含备用CA的指纹,可以临时切换到具有不同证书的其他域。

总结

  • Certificate Pinning — 固定可信证书或其公钥以防止通过伪造CA进行的MITM攻击
  • 两种方法 — certificate pinning(严格,对证书)和public key pinning(灵活,对公钥)
  • 备用pin必需 — 至少2个指纹以确保证书轮换时的连续性
  • OkHttp CertificatePinner — Android上的标准实施方法,支持多个pin
  • URLSessionDelegate — iOS上的主要方法,手动检查SecTrust和SHA-256哈希
  • 错误 — 缺乏备用pin、无混淆存储、debug/release配置混淆
  • 建议 — 对大多数项目使用public key pinning,仅对关键系统使用Certificate Pinning

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读