第3章 证书公钥锁定与证书文件锁定的区别
3.1 两种锁定的核心概念
SSL Pinning 的核心是客户端在握手时校验服务端证书/公钥是否在白名单内。根据校验粒度的不同,分为 Certificate Pinning(证书文件锁定) 和 Public Key Pinning(公钥锁定) 两种方式。它们的差别决定了更新策略、安全强度、兼容性以及在支付 SDK 中的实际选型。
一句话总结:证书锁定校验的是整个证书文件(含有效期、颁发者、序列号等全部字段);公钥锁定只校验证书中的 RSA/ECC 公钥,不依赖证书元数据,有效期更长。
3.2 对比分析表
| 对比维度 | Certificate Pinning | Public Key Pinning |
|---|---|---|
| 锁定对象 | 整个 X.509 证书(DER/Base64 编码) | 证书中的公钥(SubjectPublicKeyInfo) |
| 验证方式 | 客户端存储证书指纹或完整证书,握手时比对逐字节 | 客户端存储公钥指纹,比对证书中的公钥字段 |
| 更新灵活性 | 低:证书变更(如续期、更换 CA)必须同步更新客户端 | 高:只要公私钥对不变,更换 CA 或证书格式无需更新客户端 |
| 安全强度 | 强:锁定对象更细、攻击者伪造证书链的难度更大 | 较强:仅锁公钥,攻击者仍可用不同证书但相同公钥(理论上难度极高) |
| 兼容性 | 依赖证书的完整性和序列号,可能受中间证书变动影响 | 跨 CA、跨平台兼容性好,只需保留公钥指纹即可 |
| 适用场景 | 证书生命周期短、需严控证书链完整性的场景(如金融支付内网) | 长期服务、证书经常轮换、需要客户端少更新的移动支付 SDK |
| 实现复杂度 | 需处理证书格式、链顺序、过期校验 | 仅需提取 SubjectPublicKeyInfo,算法通用 |
3.3 验证流程对比(SVG)
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 的初始阶段,推荐使用 公钥锁定 作为默认方案,并预埋两个公钥(当前+下个轮换期)。在测试环境中启用 证书锁定 进行完整性校验,上线前通过开关切换。切勿直接在生产环境使用证书锁定而不预留紧急降级手段。