4. OkHttp CertificatePinner 配置:API 解析、构建对象、域名与哈希值设置、多证书备份

CertificatePinner 是 OkHttp 提供的证书固定核心类,用于将 HTTPS 连接的公钥指纹(SHA-256 哈希)和域名进行绑定。当客户端发起连接时,OkHttp 会校验服务端证书链中某个公钥的哈希值是否与本地配置的指纹匹配,若不匹配则立即中断连接并抛出 SSLHandshakeException。

4.1 API 解析

方法 / 构造器 说明
CertificatePinner.Builder 建造者模式,用于逐步添加固定规则
.add(String domain, String... pins) 为域名添加一个或多个公钥哈希(格式:sha256/...)
.build() 返回不可变的 CertificatePinner 实例
CertificatePinner.check(String hostname, List<Certificate> peerCertificates) 运行时校验逻辑,通常由 OkHttp 内部调用

4.2 构建 CertificatePinner 对象

  1. 创建 CertificatePinner.Builder 实例。
  2. 调用 .add(domain, pin) 添加至少一条固定规则。
  3. 调用 .build() 生成不可变对象。
  4. 将 CertificatePinner 注入到 OkHttpClient.Builder.certificatePinner()。
关键:必须同时保留服务端有效证书链的至少一个公钥哈希,否则客户端将无法建立连接。

4.3 域名与哈希值设置

域名支持通配符 *,例如 *.example.com 可匹配 api.example.com、cdn.example.com 等子域名。哈希值必须使用 sha256/ 前缀后跟 Base64 编码的公钥 SHA-256 指纹。获取指纹的经典命令:

openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null \
  | openssl x509 -pubkey -noout \
  | openssl pkey -pubin -outform der \
  | openssl dgst -sha256 -binary \
  | openssl enc -base64

将输出的 Base64 字符串填入 sha256/ 后面即可:

CertificatePinner pinner = new CertificatePinner.Builder()
    .add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
    .build();

4.4 多个备份证书

为避免证书更换导致客户端全部失效,应同时固定两个以上备份公钥。通常的做法是:
主证书:当前线上使用的证书公钥;
备用证书:即将上线或同一 CA 签发的其他证书公钥;
中间 CA 公钥:固定签发机构中间级证书的公钥,可实现更长的有效期跨度。

CertificatePinner backupPinner = new CertificatePinner.Builder()
    .add("api.example.com",
         "sha256/AAAAprimaryHash=",      // 主证书
         "sha256/BBBBbackupHash=",       // 备用证书
         "sha256/CCCCintermediateHash=") // 中间 CA 公钥
    .build();
建议:至少保留 2~3 个哈希值(含备份),且定期轮换更新。推荐将中间 CA 公钥固定,减少频繁更新客户端成本。

4.5 完整配置流程(内联 SVG 框架图)

CertificatePinner 配置流程图 获取公钥指纹 创建 Builder 实例 .add(domain, pin1, pin2, ...) .build() → CertificatePinner 注入 OkHttpClient 运行时校验(自动) ⚠️ 多备份原则 主证书 + 备用证书 + 中间 CA 公钥 避免单点失效

4.6 调试与验证

配置完成后,可以通过抓包或 OkHttp 的日志拦截器观察 SSL 握手过程。若固定失败,OkHttp 会抛出:

javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
  Peer certificate chain does not match any pinned public key.

使用 HttpLoggingInterceptor 级别设为 BODY 可查看具体握手状态码(如 200 成功,证书错误会直接抛出异常)。

常见错误:
• 固定了过期的证书公钥,导致证书更新后客户端无法连接。
• 使用了错误格式的哈希值(缺少 sha256/ 前缀或 Base64 不完整)。
• 域名未精确匹配(注意 *.example.com 不能匹配 example.com 本身)。

4.7 性能与稳定性优化

  • 预计算指纹:在编译期或构建脚本中生成指纹文件,避免运行时动态计算。
  • 配置下沉:将固定规则写入 res/raw/ 或远端配置文件,通过 A/B 测试灰度更新。
  • 日志降级:正式发布时关闭 HttpLoggingInterceptor,避免每次握手打印证书链造成性能损耗。
  • 备用证书轮换:保持至少一个备用证书的有效期超过 6 个月,减少紧急更新场景。