1. HTTPS 证书固定原理

证书固定(Certificate Pinning)是一种将客户端(支付 SDK)与特定服务器证书或公钥绑定的安全策略。在支付场景中,它能够有效防止中间人攻击(MITM),确保交易数据只被真实的支付网关处理。理解其原理需要先掌握 TLS 握手流程、证书链验证机制,以及与动态信任的差异。

1.1 TLS 握手核心流程

TLS 1.2/1.3 握手的主要步骤(忽略密钥协定细节):

  1. ClientHello:客户端发送支持的 TLS 版本、密码套件和随机数。
  2. ServerHello + Certificate:服务端选定版本/套件,发送其数字证书(含公钥)。
  3. 证书链验证:客户端内置的 CA 信任存储对服务端证书链逐级认证。
  4. 密钥交换:客户端生成预主密钥,用服务端公钥加密后发送;服务端解密。
  5. Finished:双方完成握手,开始加密通信。

其中第 3 步是静态信任的核心——客户端依赖系统 CA 列表判断证书是否由受信任的根签发。但这种方式存在风险:如果攻击者能控制系统 CA 列表(如植入恶意根证书),则可冒充支付服务器。

1.2 证书链验证机制

层级角色验证要点
叶子证书最终服务器证书域名匹配、有效期、密钥用法
中间证书签发叶子证书的 CA必须由受信任的根 CA 签发
根证书CA 信任锚点客户端预置的根证书(自签信任)

标准验证流程:客户端从叶子证书向上构建链,直到找到一个本地信任的根证书;同时检查每级证书的签名、有效期和 CRL/OCSP 吊销状态。如果链中任何一环失败,握手终止。

支付场景的难点:支付网关的证书可能由多家 CA 签发(如备用证书轮换),而系统 CA 列表会随操作系统更新变化。单纯信任系统 CA 无法防范“中间人使用了合法的被信任 CA 颁发的假证书”的攻击。

1.3 固定与动态信任的区别

对比维度动态信任(标准 TLS)证书固定(Pinning)
信任基础系统 CA 存储硬编码的证书指纹或公钥
灵活性高,CA 可随时签发新证书低,变更需升级 SDK
安全性受 CA 生态影响(CA 被攻陷则危险)强,仅接受特定证书/公钥
运维成本低,证书自动续期需要预置多个备用指纹并定期更新
对中间人攻击的防护弱(若 CA 被滥用)强(即使 CA 信任链被破坏)

1.4 证书固定策略的两种形式

  • 证书固定(Certificate Pinning):直接固定服务器叶子证书的 SHA-256 指纹。证书过期或更换时需要更新 SDK。
  • 公钥固定(Public Key Pinning):固定证书中公钥(SPKI)的哈希。证书更换但公钥不变时仍可正常通信,更灵活。
⚠ 关键警告:支付 SDK 中固定公钥时需要同时预置多个备用公钥(至少一个生产、一个灾备),避免因服务端证书轮换导致所有支付请求失败。推荐使用 HPKP(HTTP Public Key Pinning)的备份机制,但注意 HPKP 已被弃用,现代移动端建议使用本地代码硬编码。

1.5 TLS 握手与证书固定交互流程图

TLS 握手与证书固定流程 ClientHello ServerHello + 证书 系统 CA 验证链 通过? 密钥交换 握手失败 证书固定检查 指纹匹配? 继续握手 阻断连接 说明:支付 SDK 在标准证书链验证通过后,额外执行固定检查;指纹不匹配则直接阻断,不进入密钥交换

1.6 支付 SDK 中的固定实现要求

  • 固定内容:推荐固定公钥的 SPKI 指纹(SHA-256 编码),而非整个证书。
  • 备份机制:至少预置 2-3 个备用公钥,允许服务端在证书轮换时顺利过渡。
  • 更新通道:通过应用商店或热修复更新固定的指纹列表,避免长期依赖同一组。
  • 调试模式:开发阶段可临时关闭固定检查,但生产包绝不能去掉该逻辑。
💡 最佳实践:使用 Android 的 CertificatePinner(OkHttp)或 iOS 的 URLSession 的 SecTrustEvaluate 自定义验证回调来实施固定。避免自己实现复杂的证书链解析,利用框架提供的接口更安全。

1.7 常见误区

误区说明
固定域名而非证书域名固定无法抵御 DNS 劫持与伪造证书攻击
只固定叶子证书指纹证书更换时支付功能立即中断;应同时固定多个指纹
混淆固定与双向验证固定是客户端验证服务器,双向验证还需要服务端验证客户端证书