第3章 证书公钥锁定与证书文件锁定的区别

3.1 两种锁定的核心概念

SSL Pinning 的核心是客户端在握手时校验服务端证书/公钥是否在白名单内。根据校验粒度的不同,分为 Certificate Pinning(证书文件锁定) 和 Public Key Pinning(公钥锁定) 两种方式。它们的差别决定了更新策略、安全强度、兼容性以及在支付 SDK 中的实际选型。

一句话总结:证书锁定校验的是整个证书文件(含有效期、颁发者、序列号等全部字段);公钥锁定只校验证书中的 RSA/ECC 公钥,不依赖证书元数据,有效期更长。

3.2 对比分析表

对比维度Certificate PinningPublic Key Pinning
锁定对象整个 X.509 证书(DER/Base64 编码)证书中的公钥(SubjectPublicKeyInfo)
验证方式客户端存储证书指纹或完整证书,握手时比对逐字节客户端存储公钥指纹,比对证书中的公钥字段
更新灵活性低:证书变更(如续期、更换 CA)必须同步更新客户端高:只要公私钥对不变,更换 CA 或证书格式无需更新客户端
安全强度强:锁定对象更细、攻击者伪造证书链的难度更大较强:仅锁公钥,攻击者仍可用不同证书但相同公钥(理论上难度极高)
兼容性依赖证书的完整性和序列号,可能受中间证书变动影响跨 CA、跨平台兼容性好,只需保留公钥指纹即可
适用场景证书生命周期短、需严控证书链完整性的场景(如金融支付内网)长期服务、证书经常轮换、需要客户端少更新的移动支付 SDK
实现复杂度需处理证书格式、链顺序、过期校验仅需提取 SubjectPublicKeyInfo,算法通用

3.3 验证流程对比(SVG)

Certificate Pinning 与 Public Key Pinning 验证流程 两种 Pinning 验证流程对比 服务器证书 提取完整证书 与本地存储的证书指纹 逐字节比对 匹配 → 通过 不匹配 → 拒绝 提取公钥 (SPKI) 与本地存储的公钥指纹 比对 匹配 → 通过 不匹配 → 拒绝 公钥锁定仅依赖密钥对,证书轮换时若公私钥不变则客户端无需更新, 适合支付 SDK 的长周期离线场景;证书锁定更严格但维护成本高。

3.4 安全强度深入分析

从攻击面的角度对比两种锁定方式的安全性:

  • Certificate Pinning 锁定的是完整证书,即使攻击者获得了原始私钥,也无法使用自签名证书或中间人伪造的证书。因为证书的签发者、有效期、序列号等所有字段都必须匹配。但缺点是一旦证书需要更新(如 CA 根证书变更),客户端必须同步发布新版本,否则会连接失败。
  • Public Key Pinning 只锁定公钥。如果攻击者窃取了私钥,可以签发任意证书并配上相同公钥,此时客户端仍会通过验证(尽管攻击者需要获得私钥)。但在实际攻击难度上,私钥泄露本身已是重大安全事件;对于防范 CA 误签发 的场景,公钥锁定同样有效——只要攻击者无法伪造具备相同公钥的合法证书。
支付 SDK 安全警告: 切勿采用只锁根 CA 或仅锁证书链的弱 pinning 方案。建议在生产环境中至少锁定 2 个备用公钥(一个当前、一个备用),防止因密钥轮换导致服务中断。纯公钥锁定适用于需要长期离线更新的支付场景(如 POS 终端),证书锁定适合服务端证书变更可控的内部系统。

3.5 代码示例:Android OkHttp 中的配置差异

在 Android 支付 SDK 中,使用 OkHttp 的 CertificatePinner 即可实现两种锁定:

// --- 证书锁定(Certificate Pinning)---
// 你需要提前计算证书的 SHA-256 指纹(整个证书)
CertificatePinner certificatePinner = new CertificatePinner.Builder()
    .add("api.pay.example.com", "sha256/AAAA...")  // 证书指纹
    .build();

// --- 公钥锁定(Public Key Pinning)---
// 提取证书中的 SubjectPublicKeyInfo 的 SHA-256 指纹
// 可以通过 openssl 命令获取:
// openssl x509 -in cert.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
CertificatePinner publicKeyPinner = new CertificatePinner.Builder()
    .add("api.pay.example.com", "sha256/BBBB...")  // 公钥指纹
    .build();

3.6 适用场景建议

场景推荐方案理由
支付 SDK 集成在第三方 App(版本不可控)公钥锁定 + 备用公钥避免证书更新导致用户无法支付,降低运维风险
企业内部支付网关(证书可控、可同步发布更新)证书锁定安全颗粒度最高,防止私钥泄露后的复合攻击
支付 SDK 同时支持多个证书颁发机构(备份 CA)公钥锁定保持公钥不变即可切换 CA,客户端无需更新
高风险交易(如大额转账、钱包充值)双锁定:证书 + 公钥同时比对双重校验可抵御证书伪造和公钥复用攻击,但需谨慎更新策略
实践建议: 在支付 SDK 的初始阶段,推荐使用 公钥锁定 作为默认方案,并预埋两个公钥(当前+下个轮换期)。在测试环境中启用 证书锁定 进行完整性校验,上线前通过开关切换。切勿直接在生产环境使用证书锁定而不预留紧急降级手段。

3.7 本章小结