Certificate Pinning(证书固定)——是一种安全技术,移动应用通过它检查服务器证书是否与预先已知的样本匹配,而不是简单地信任CA链中的任何证书。与依赖数百个认证中心的普通TLS验证不同,pinning将信任范围缩小到一个特定的证书或其公钥。根据OWASP移动安全测试指南(2024),实施Certificate Pinning可关闭100%与证书替换相关的中间人攻击场景。 OWASP MSTG, 2024
要点
Certificate Pinning——是一种安全机制,应用存储(或"嵌入"——pin)服务器证书的样本,并在每次连接时将接收到的证书与此样本进行比较。如果证书不匹配——即使它由受信任的认证中心正式签名——连接也会被中断。这可以防止攻击者通过受损CA获取虚假证书的攻击(如2011年的DigiNotar或2011年的Comodo事件)。
Pinning过程包括三个阶段:从可信实例中提取证书或公钥的指纹;将该指纹存储在应用的代码或资源中;在TLS握手阶段进行比较。开发者可以固定整个证书的SHA-256指纹或仅公钥的指纹(公钥固定)。第二种方法更优:续期证书时公钥通常保持不变,应用不会失去与服务器的连接。根据OWASP建议,最小pin数量为2:一个当前和一个备用用于密钥轮换。现代库如OkHttp和TrustKit自动在每次TLS连接时验证指定的pin,无需开发者额外投入。重要的是要理解pinning不会替代标准TLS验证,而是补充它:首先执行常规握手和证书链验证,然后进行额外的pinning验证。这种双层保护消除了与CA受损相关的漏洞,包括错误颁发证书的情况和对认证中心基础设施的攻击。
实现Certificate Pinning有几种方法,每种方法都有其存储和验证特点。方法的选择取决于应用架构、证书更新频率和灵活性要求。
| Pinning类型 | 存储内容 | 灵活性 | 使用示例 |
|---|---|---|---|
| Certificate Pinning | 整个X.509证书 | 低 | 固定证书1–2年 |
| Public Key Pinning | 公钥(SPKI) | 中 | OWASP推荐的方法 |
| Hash Pinning | SHA-256指纹 | 中 | 在OkHttp中流行(certificatePinner) |
| CA Pinning | 中间CA | 高 | 企业应用 |
最平衡的方法是公钥固定(Public Key Pinning),由OWASP和Google推荐。应用存储SubjectPublicKeyInfo指纹(公钥的抽象),而不是特定证书(每1–2年更改一次)。如果证书使用相同密钥续期(密钥重用),pin保持有效。如果密钥更改——开发者在应用更新中提前添加备用pin。在移动项目中采用最小/最大pin策略:至少2个pin(包括备用),最多4个,以防止膨胀和验证时间增加。
选择特定pinning类型取决于应用的架构和要求。对于通过一个域与REST API通信的公共移动应用,通过OkHttp或TrustKit使用两个pin的公钥固定是最优的。对于拥有自己的认证中心的企业应用,CA固定更合适——客户端证书更改时无需更新,因为信任绑定到CA而不是最终证书。对于IoT和嵌入式系统,建议使用固定完整证书的Certificate Pinning:设备很少更新,因此对完整信任链的控制至关重要。监控pin的到期日期——强制性实践:在证书到期前30、14和7天设置警报,以便在当前证书失效前发布带有新pin的应用更新。为自动化发布带有新pin的更新,建议使用Firebase Remote Config或自己的配置API,可以在不发布应用商店新版本的情况下动态更新pin列表。
Certificate Pinning显著提高移动应用的安全性,但给开发团队带来运营负担。重要的是权衡保护的好处和错误实施时连接锁定的风险。
主要优点——防御中间人攻击,包括CA受损的情况。Pinning使攻击者颁发的虚假证书无效:即使CA签署了伪造品,应用也会拒绝它。额外的好处——防御为检查流量而替换证书的企业代理服务器。根据Google安全博客(2023),使用pinning的应用相比仅使用标准TLS验证的应用,通过流量拦截被黑客攻击的可能性低86%。
Pinning的主要缺点——自我锁定的风险:如果在应用更新发布前服务器证书发生更改(续期、更换提供商、密钥轮换),用户将失去对服务器的访问。其他缺点:调试困难(每次设置更改都需要更新pin),使用TrustKit时APK大小增加5–15 KB,以及无法在没有新版本的情况下快速回滚更改。为最小化风险,使用备用pin、每2–3个月自动轮换以及应用同时接受旧证书和新证书的宽限期。还需注意,在启用pinning的开发过程中,无法使用代理工具(Burp Suite、Charles)调试网络请求——对于开发版本,应通过BuildConfig.DEBUG标志禁用pinning,QA测试应在启用保护的发布签名上进行。一些团队使用带有单独pinning证书的staging域用于开发环境,以在开发阶段也保持保护。
让我们看看在Android上使用OkHttp(网络请求的标准库)实现Certificate Pinning的示例。OkHttp提供内置的CertificatePinner,接受公钥的SHA-256哈希值。
val certificatePinner = CertificatePinner.Builder()
.add(
"api.example.com",
"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
)
.add(
"api.example.com",
"sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="
)
.build()
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
在上面的代码中,我们为api.example.com域添加两个pin:主要(当前证书)和备用(用于轮换)。OkHttp自动检查服务器证书是否与指定的SHA-256指纹之一匹配。获取证书SHA-256指纹的命令:openssl s_client -connect api.example.com:443 | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64。重要的是不要在代码中以开放形式存储指纹,而应加密或混淆:MobSF静态分析容易在DEX文件中找到裸SHA-256字符串。建议将pin存储在res/raw资源中,通过AES加密,并在应用启动时通过本地代码(NDK/JNI)解密。
在iOS上,Certificate Pinning的主要工具是开源库TrustKit。与OkHttp不同,TrustKit通过Info.plist声明式配置,允许在不重新编译应用的情况下更改pin。配置包括包含域名的字典和公钥SHA-256指纹的数组。TrustKit自动拦截NSURLSession请求,并在数据传输开始前检查证书。TrustKit的一个关键特性——支持pin验证报告:库可以在pin不匹配时向指定端点发送报告,从而快速响应证书异常。Apple也从iOS 14开始提供Info.plist中的原生NSPinnedDomains机制,但TrustKit由于更灵活的配置、报告支持以及无需系统更新即可热替换pin的能力,仍是首选。TrustKit通过didReceiveChallenge委托与URLSession集成,pin验证成功时返回.performDefaultHandling,不匹配时返回.cancelAuthenticationChallenge。为监控pin验证报告,建议配置一个单独的端点来分析错误频率:如果报告数量突然增加——可能表示中间人攻击或证书即将过期,需要立即更新pin。
常见问题
Certificate Pinning——就像在手机中保存朋友的指纹:你记住服务器"正确"证书的样子,不再信任任何人,即使有人出示来自"官方"中心的证件。
普通HTTPS信任来自数百个中心的任何CA签名的任何证书。Certificate Pinning添加了"来自上方"的验证:证书不仅必须有效,而且必须是在应用代码中固定的那个特定证书。
建议存储2–3个pin:当前和用于新证书的备用pin。在证书更改前1–2个月发布添加了未来证书pin的新版应用。更改后,旧pin将从下一个版本中删除。
可以。Pinning适用于任何证书,包括Let's Encrypt。重要的是要记住免费证书的有效期很短(3个月),因此备用pin策略和自动轮换成为强制性的。
使用Burp Suite或mitmproxy测试pinning。如果应用正确配置了pinning,代理工具将无法拦截流量——连接将在握手阶段中断。对于集成测试,使用来自OkHttp的MockWebServer。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。