如何在Flutter中不使用插件实现SSL公钥固定?
Flutter 公钥固定实现方案(无插件)
一、Dart 原生方法实现(无需依赖插件)
Dart 自带的 dart:io 库提供了 SecurityContext 和 HttpClient,可直接实现公钥固定——核心逻辑是将服务器公钥的指纹加入可信列表,发起请求时验证服务器证书的公钥是否匹配。
步骤说明
提取服务器公钥的 SHA-256 指纹
通过命令行工具(如openssl)获取:openssl s_client -connect your-domain.com:443 < /dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64输出的字符串即为公钥的 SHA-256 指纹(例如
abc123...xyz)。配置自定义 SecurityContext 并发起请求
创建带可信指纹验证的HttpClient,代码示例:import 'dart:io'; import 'dart:convert'; import 'package:crypto/crypto.dart'; Future<void> makeSecureRequest() async { // 替换为你的服务器公钥 SHA-256 指纹 const trustedPublicKeyFingerprint = "your-public-key-fingerprint"; final securityContext = SecurityContext(); // 加载系统默认根证书(可选,避免仅信任单一公钥导致的兼容性问题) securityContext.setTrustedCertificatesBytes(await rootCertificates); final httpClient = HttpClient(context: securityContext); // 手动验证公钥指纹 httpClient.badCertificateCallback = (X509Certificate cert, String host, int port) { // 计算当前证书公钥的 SHA-256 指纹 final publicKeyBytes = base64Decode(cert.publicKey); final sha256Digest = sha256.convert(publicKeyBytes).bytes; final currentFingerprint = base64Encode(sha256Digest); // 对比指纹是否匹配,返回true表示信任该证书 return currentFingerprint == trustedPublicKeyFingerprint; }; try { final request = await httpClient.getUrl(Uri.parse("https://your-domain.com/api")); final response = await request.close(); final responseBody = await utf8.decodeStream(response); print(responseBody); } catch (e) { print("请求失败: $e"); } finally { httpClient.close(); } }
二、通过 Method Channel 调用自定义原生代码实现
如果需要全局拦截网络请求或更底层的控制,可通过 Method Channel 让 Flutter 调用 Android/iOS 的原生网络逻辑,在原生层实现公钥固定。
Android 端实现(基于 OkHttp)
创建公钥验证拦截器
class PublicKeyPinningInterceptor : Interceptor { // 替换为你的服务器公钥 SHA-256 指纹 private val trustedFingerprint = "your-public-key-fingerprint" override fun intercept(chain: Interceptor.Chain): Response { val response = chain.proceed(chain.request()) val certificate = response.handshake?.peerCertificates?.firstOrNull() ?: throw SSLException("无法获取服务器证书") // 提取公钥并计算 SHA-256 指纹 val publicKeyBytes = certificate.publicKey.encoded val sha256Digest = MessageDigest.getInstance("SHA-256").digest(publicKeyBytes) val currentFingerprint = Base64.encodeToString(sha256Digest, Base64.NO_WRAP) if (currentFingerprint != trustedFingerprint) { throw SSLException("公钥指纹不匹配,请求被拦截") } return response } }注册 Method Channel 处理 Flutter 请求
class MainActivity : FlutterActivity() { private val CHANNEL = "com.example/secure_network" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> if (call.method == "makeSecureRequest") { val url = call.argument<String>("url") ?: run { result.error("INVALID_URL", "URL 不能为空", null) return@setMethodCallHandler } val okHttpClient = OkHttpClient.Builder() .addInterceptor(PublicKeyPinningInterceptor()) .build() val request = Request.Builder().url(url).build() try { val response = okHttpClient.newCall(request).execute() val responseBody = response.body?.string() ?: "" result.success(responseBody) } catch (e: Exception) { result.error("REQUEST_FAILED", e.message, null) } } else { result.notImplemented() } } } }
iOS 端实现(基于 NSURLSession)
创建带公钥验证的网络管理器
class SecureNetworkManager: NSObject, URLSessionDelegate { // 替换为你的服务器公钥 SHA-256 指纹 private let trustedFingerprint = "your-public-key-fingerprint" func makeRequest(url: String, completion: @escaping (String?, Error?) -> Void) { guard let url = URL(string: url) else { completion(nil, NSError(domain: "INVALID_URL", code: -1, userInfo: [NSLocalizedDescriptionKey: "URL 无效"])) return } let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) let task = session.dataTask(with: url) { data, _, error in if let error = error { completion(nil, error) return } completion(data.flatMap { String(data: $0) } ?? "", nil) } task.resume() } func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { guard let serverTrust = challenge.protectionSpace.serverTrust, let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0), let publicKey = SecCertificateCopyPublicKey(certificate), let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil) as Data? else { completionHandler(.cancelAuthenticationChallenge, nil) return } // 计算公钥 SHA-256 指纹 let sha256Digest = SHA256.hash(data: publicKeyData) let currentFingerprint = Data(sha256Digest).base64EncodedString() if currentFingerprint == trustedFingerprint { completionHandler(.useCredential, URLCredential(trust: serverTrust)) } else { completionHandler(.cancelAuthenticationChallenge, nil) } } }注册 Method Channel 处理 Flutter 请求
@UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { private let CHANNEL = "com.example/secure_network" override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let controller = window?.rootViewController as! FlutterViewController let methodChannel = FlutterMethodChannel(name: CHANNEL, binaryMessenger: controller.binaryMessenger) methodChannel.setMethodCallHandler { call, result in if call.method == "makeSecureRequest" { guard let url = call.arguments as? String else { result.error("INVALID_URL", "URL 不能为空", nil) return } let networkManager = SecureNetworkManager() networkManager.makeRequest(url: url) { responseBody, error in if let error = error { result.error("REQUEST_FAILED", error.localizedDescription, nil) } else { result.success(responseBody) } } } else { result.notImplemented() } } GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }
Flutter 端调用 Method Channel
import 'package:flutter/services.dart'; class SecureNetworkService { static const MethodChannel _channel = MethodChannel('com.example/secure_network'); static Future<String?> makeSecureRequest(String url) async { try { final String? response = await _channel.invokeMethod('makeSecureRequest', url); return response; } on PlatformException catch (e) { print("请求失败: ${e.message}"); return null; } } } // 使用示例 void main() async { final response = await SecureNetworkService.makeSecureRequest("https://your-domain.com/api"); print(response); }
内容的提问来源于stack exchange,提问作者Nachiket Gohil
相关产品推荐
相关产品推荐

