在 OkHttp 中,CertificatePinner
是一个安全功能,用于实现 SSL/TLS 证书固定(Certificate Pinning) 。通过将服务器的公钥或证书与客户端预定义的可信值绑定,可以有效防止中间人攻击(MITM),即使攻击者获得了伪造的可信 CA 证书,也无法欺骗客户端。
1. CertificatePinner
的作用
-
增强 HTTPS 安全性:
- 即使服务器的证书被替换或签发机构被攻破,客户端依然能够验证预定义的可信证书。
-
防止中间人攻击:
- 防止攻击者通过伪造的证书截获和篡改数据。
-
提升安全透明性:
- 明确指定客户端信任的证书或公钥,减少对 CA 的完全信任。
2. CertificatePinner
的基本工作原理
- 客户端在发起 HTTPS 请求时,会从服务器获取其证书。
CertificatePinner
将服务器证书的公钥或完整证书与客户端本地预定义的值进行对比。- 如果匹配成功,连接继续;否则,连接失败。
3. CertificatePinner
的使用
1. 创建 CertificatePinner
CertificatePinner
是不可变的,通过 CertificatePinner.Builder
配置:
java
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build();
-
add()
方法参数:hostname
:目标主机名(如example.com
)。pin
:证书的指纹(支持sha256
和sha1
格式,但推荐使用sha256
)。
2. 将 CertificatePinner
应用于 OkHttpClient
将 CertificatePinner
设置到 OkHttpClient
:
java
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build();
3. 获取服务器证书指纹
要固定证书,需要先获取服务器证书的指纹:
-
使用 OpenSSL:
bashbash 复制代码 echo | openssl s_client -connect example.com:443 | openssl x509 -pubkey | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -binary | base64
-
使用 Java 程序提取:
javaOkHttpClient client = new OkHttpClient(); Request request = new Request.Builder().url("https://example.com").build(); Response response = client.newCall(request).execute(); Handshake handshake = response.handshake(); List<Certificate> certificates = handshake.peerCertificates(); for (Certificate certificate : certificates) { System.out.println(certificate); }
4. 完整示例
java
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.add("api.example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")
.build();
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build();
Request request = new Request.Builder()
.url("https://example.com")
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}
5. 注意事项
1. 主机名匹配
-
CertificatePinner
支持通配符:java.add("*.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
2. 动态证书更新
- 如果服务器的证书更新,客户端需要同步更新
CertificatePinner
,否则会导致连接失败。
3. 多个指纹支持
-
可以为同一个主机配置多个指纹,以应对证书过渡或多 CA 签发的情况:
java.add("example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=");
4. 生产环境推荐
- 固定公钥(
pin
),而非完整证书,以减少证书更换的频率。
5. 禁用证书固定
-
在开发或测试环境中,可以禁用
CertificatePinner
,例如设置一个空实例:javaOkHttpClient client = new OkHttpClient.Builder() .certificatePinner(CertificatePinner.DEFAULT) .build();
6. 常见问题与解决
问题 1:连接失败,提示证书固定错误
- 确保固定的指纹与服务器证书的实际指纹一致。
- 检查主机名是否匹配,确保域名正确。
问题 2:证书更换导致服务中断
- 提前添加新证书的指纹,支持证书过渡。
7. CertificatePinner
的优势与限制
优势
- 增强安全性,防止中间人攻击。
- 降低对 CA 的信任依赖。
限制
- 配置和维护成本较高,特别是证书频繁更换时。
- 不支持动态调整,证书变化需重新部署客户端。