如何在Flutter中实现SSL公钥固定?
嘿,我之前也踩过Flutter SSL公钥固定的坑,给你整理了一套亲测有效的步骤,分Dart通用层和原生平台配置两部分,覆盖所有场景:
Flutter 实现 SSL 公钥固定完整指南
一、Dart 通用层配置(适用于所有网络请求)
不管你用官方http包还是dio,核心思路都是验证服务器返回证书的公钥是否在我们预先允许的列表中,避免信任系统默认的所有证书。
1. 获取目标服务器的公钥
先用openssl命令提取服务器证书的公钥(直接复制输出的字符串即可):
# 提取公钥本身的Base64编码(用于Dart代码验证) openssl s_client -connect example.com:443 < /dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl base64 -A
2. 用官方http包实现固定
创建自定义的HttpClient,通过badCertificateCallback来验证公钥:
import 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; // 预先存储允许的公钥列表(建议存多个,防止证书更新导致服务不可用) final allowedPublicKeys = { "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz...", // 你的主公钥 "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv..." // 备用公钥 }; void main() async { // 初始化自定义HttpClient HttpClient httpClient = HttpClient() ..badCertificateCallback = (X509Certificate cert, String host, int port) { // 提取当前证书的公钥并转Base64 String currentPublicKey = base64.encode(cert.publicKey); // 检查是否在允许列表内 return allowedPublicKeys.contains(currentPublicKey); }; // 包装成http.Client使用 http.Client client = http.IOClient(httpClient); try { final response = await client.get(Uri.parse("https://example.com")); print("请求成功,状态码: ${response.statusCode}"); } catch (e) { print("请求失败: $e"); } }
3. 用Dio实现固定
如果用Dio,只需要在初始化时配置onHttpClientCreate:
import 'dart:convert'; import 'dart:io'; import 'package:dio/dio.dart'; final allowedPublicKeys = { "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz...", }; void main() async { Dio dio = Dio() ..httpClientAdapter = IOHttpClientAdapter( createHttpClient: () { return HttpClient() ..badCertificateCallback = (cert, host, port) { String currentPublicKey = base64.encode(cert.publicKey); return allowedPublicKeys.contains(currentPublicKey); }; }, ); try { Response response = await dio.get("https://example.com"); print("请求成功: ${response.data}"); } catch (e) { print("请求失败: $e"); } }
二、Android 原生层配置(覆盖WebView/原生SDK请求)
如果你的App里有WebView或者第三方SDK用原生网络请求,需要配置Android的网络安全策略:
- 在
res/xml目录下创建network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config> <!-- 替换成你的域名,includeSubdomains=true表示包含子域名 --> <domain includeSubdomains="true">example.com</domain> <pin-set> <!-- 这里填公钥的SHA-256哈希Base64,获取命令见下方 --> <pin digest="SHA-256">abcdefghijklmnopqrstuvwxyz1234567890==</pin> <pin digest="SHA-256">zyxwvutsrqponmlkjihgfedcba0987654321==</pin> </pin-set> <!-- 调试阶段可以保留,发布时建议移除,强制只信任固定的公钥 --> <!-- <trust-anchors> <certificates src="system" /> </trust-anchors> --> </domain-config> </network-security-config>
- 获取Android需要的SHA-256公钥哈希:
openssl s_client -connect example.com:443 < /dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl base64
- 在
AndroidManifest.xml的application标签中添加配置:
<application android:name=".MainApplication" ... android:networkSecurityConfig="@xml/network_security_config"> ... </application>
三、iOS 原生层配置(覆盖WebView/原生SDK请求)
iOS需要在Info.plist中配置ATS(App Transport Security)规则:
- 打开
ios/Runner/Info.plist,添加以下配置:
<key>NSAppTransportSecurity</key> <dict> <key>NSExceptionDomains</key> <dict> <key>example.com</key> <dict> <key>NSIncludesSubdomains</key> <true/> <key>NSPinnedPublicKeys</key> <array> <!-- 和Android一样,填公钥的SHA-256哈希Base64 --> <string>abcdefghijklmnopqrstuvwxyz1234567890==</string> <string>zyxwvutsrqponmlkjihgfedcba0987654321==</string> </array> <key>NSExceptionAllowsInsecureHTTPLoads</key> <false/> </dict> </dict> </dict>
四、验证固定是否生效
最简单的验证方法:
- 修改
allowedPublicKeys或原生配置中的公钥为错误值 - 发起网络请求,看是否会抛出证书验证失败的错误
- 如果请求失败,说明公钥固定已经生效
内容的提问来源于stack exchange,提问作者Developer
相关产品推荐
相关产品推荐

