Flutter公钥固定插件public_key_pinning的使用

Flutter公钥固定插件public_key_pinning的使用

Https证书固定(Certificate Pinning)在Flutter中的实现可以通过public_key_pinning插件来完成。本文将介绍如何在Flutter项目中使用此插件,并提供完整的示例代码。


1. 添加依赖

在你的Flutter或Dart项目中添加以下依赖:

dependencies:
  ...
  public_key_pinning: ^2.1.3

执行flutter pub get以安装依赖。


2. 获取证书指纹

要获取服务器SSL证书的SHA256指纹,可以在终端运行以下命令:

openssl x509 -noout -fingerprint -sha256 -inform pem -in [certificate-file.crt]

例如,结果可能类似于:

'59:58:57:5A:5B:5C:5D:59:58:57:5A:5B:5C:5D:59:58:57:5A:5B:5C:5D:59:58:57:5A:5B:5C:5D:59:58:57:5A:5B:5C:5D'

3. 使用示例

3.1 使用Dio库

通过CertificatePinningInterceptor拦截器来实现证书固定:

import 'package:dio/dio.dart';
import 'package:public_key_pinning/public_key_pinning.dart';

// 配置Dio客户端并添加证书固定拦截器
Dio getClient(String baseUrl, List<String> allowedSHAFingerprints) {
  var dio = Dio(BaseOptions(baseUrl: baseUrl))
    ..interceptors.add(CertificatePinningInterceptor(allowedSHAFingerprints));
  return dio;
}

void myRepositoryMethod() {
  final dio = getClient("https://example.com", [
    "59:58:57:5A:5B:5C:5D:59:58:57:5A:5B:5C:5D:59:58:57:5A:5B:5C:5D:59:58:57:5A:5B:5C:5D:59:58:57:5A:5B:5C:5D"
  ]);
  dio.get("/api/data").then((response) {
    print(response.data);
  }).catchError((error) {
    print(error);
  });
}

3.2 使用Http库

通过SecureHttpClient来实现证书固定:

import 'package:http/http.dart' as http;
import 'package:public_key_pinning/secure_http_client.dart';

// 配置安全HTTP客户端
SecureHttpClient getClient(List<String> allowedSHAFingerprints) {
  final secureClient = SecureHttpClient.build(allowedSHAFingerprints);
  return secureClient;
}

void myRepositoryMethod() {
  final secureClient = getClient([
    "59:58:57:5A:5B:5C:5D:59:58:57:5A:5B:5C:5D:59:58:57:5A:5B:5C:5D:59:58:57:5A:5B:5C:5D:59:58:57:5A:5B:5C:5D"
  ]);
  secureClient.get("https://example.com/api/data").then((response) {
    print(response.body);
  }).catchError((error) {
    print(error);
  });
}

3.3 自定义实现

如果你使用的是其他HTTP客户端,可以调用HttpCertificatePinning.check方法进行证书固定检查:

import 'package:public_key_pinning/public_key_pinning.dart';

Future<bool> myCustomImplementation(String url, Map<String, String> headers, List<String> allowedSHAFingerprints) async {
  try {
    final secure = await HttpCertificatePinning.check(
      serverURL: url,
      headerHttp: headers,
      sha: SHA.SHA256,
      allowedSHAFingerprints: allowedSHAFingerprints,
      timeout: 50,
    );

    if (secure.contains("CONNECTION_SECURE")) {
      return true;
    } else {
      return false;
    }
  } catch (e) {
    return false;
  }
}

4. 完整示例代码

以下是一个完整的示例代码,展示如何在Flutter应用中使用public_key_pinning插件:

import 'package:flutter/material.dart';
import 'package:public_key_pinning/public_key_pinning.dart';

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  [@override](/user/override)
  _MyAppState createState() => _MyAppState();
}

class _PiningSslData {
  String serverURL = '';
  Map<String, String> headerHttp = {};
  String allowedSHAFingerprint = '';
  int timeout = 0;
  SHA? sha;
}

class _MyAppState extends State<MyApp> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  final _PiningSslData _data = _PiningSslData();
  final _messengerKey = GlobalKey<ScaffoldMessengerState>();

  [@override](/user/override)
  initState() {
    super.initState();
  }

  // 检查证书固定
  check(
    String url,
    String fingerprint,
    SHA sha,
    Map<String, String> headerHttp,
    int timeout,
  ) async {
    List<String> allowedShA1FingerprintList = [];
    allowedShA1FingerprintList.add(fingerprint);

    try {
      String checkMsg = await HttpCertificatePinning.check(
        serverURL: url,
        headerHttp: headerHttp,
        sha: sha,
        allowedSHAFingerprints: allowedShA1FingerprintList,
        timeout: timeout,
      );

      if (!mounted) return;

      _messengerKey.currentState?.showSnackBar(
        SnackBar(
          content: Text(checkMsg),
          duration: const Duration(seconds: 1),
          backgroundColor: Colors.green,
        ),
      );
    } catch (e) {
      _messengerKey.currentState?.showSnackBar(
        SnackBar(
          content: Text(e.toString()),
          duration: const Duration(seconds: 1),
          backgroundColor: Colors.red,
        ),
      );
    }
  }

  void submit() {
    if (_formKey.currentState?.validate() == true) {
      _formKey.currentState?.save();

      check(
        _data.serverURL,
        _data.allowedSHAFingerprint,
        _data.sha ?? SHA.SHA256,
        _data.headerHttp,
        _data.timeout,
      );
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      scaffoldMessengerKey: _messengerKey,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Ssl Pinning Plugin'),
        ),
        body: Builder(
          builder: (BuildContext context) {
            return Container(
              padding: const EdgeInsets.all(20.0),
              child: Form(
                key: _formKey,
                child: ListView(
                  children: <Widget>[
                    TextFormField(
                      keyboardType: TextInputType.url,
                      controller: TextEditingController(text: "https://example.com"),
                      decoration: const InputDecoration(
                        hintText: 'https://yourdomain.com',
                        labelText: 'URL',
                      ),
                      validator: (value) {
                        if (value?.isEmpty == true) {
                          return 'Please enter some url';
                        }
                        return null;
                      },
                      onSaved: (value) {
                        _data.serverURL = value ?? '';
                      },
                    ),
                    DropdownButton(
                      items: [
                        DropdownMenuItem(
                          child: Text(SHA.SHA1.toString()),
                          value: SHA.SHA1,
                        ),
                        DropdownMenuItem(
                          child: Text(SHA.SHA256.toString()),
                          value: SHA.SHA256,
                        ),
                      ],
                      value: _data.sha,
                      isExpanded: true,
                      onChanged: (SHA? val) {
                        setState(() {
                          _data.sha = val;
                        });
                      },
                    ),
                    TextFormField(
                      controller: TextEditingController(
                        text:
                            "51 E9 01 5F FE FB 79 70 D8 DF 74 BB 46 94 63 72 B1 E3 2B 31 6A 46 F0 C5 36 E7 C1 D4 DD C5 B2 70",
                      ),
                      keyboardType: TextInputType.text,
                      decoration: const InputDecoration(
                        hintText: 'OO OO OO OO OO OO OO OO OO OO',
                        labelText: 'Fingerprint',
                      ),
                      validator: (value) {
                        if (value?.isEmpty == null) {
                          return 'Please enter some fingerprint';
                        }
                        return null;
                      },
                      onSaved: (value) {
                        _data.allowedSHAFingerprint = value ?? '';
                      },
                    ),
                    TextFormField(
                      keyboardType: TextInputType.number,
                      initialValue: '60',
                      decoration: const InputDecoration(
                        hintText: '60',
                        labelText: 'Timeout',
                      ),
                      validator: (value) {
                        if (value?.isEmpty == true) {
                          return 'Please enter some timeout';
                        }
                        return null;
                      },
                      onSaved: (value) {
                        _data.timeout = int.tryParse(value ?? '') ?? 0;
                      },
                    ),
                    Container(
                      margin: const EdgeInsets.only(top: 20.0),
                      child: ElevatedButton(
                        onPressed: () => submit(),
                        child: const Text(
                          'Check',
                          style: TextStyle(color: Colors.white),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

更多关于Flutter公钥固定插件public_key_pinning的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter公钥固定插件public_key_pinning的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter中,public_key_pinning 是一个用于实现公钥固定(Public Key Pinning)的插件。公钥固定是一种安全机制,用于确保客户端只与特定的服务器通信,防止中间人攻击(MITM)。通过固定服务器的公钥,客户端可以验证服务器的身份,确保通信的安全性。

安装插件

首先,你需要在 pubspec.yaml 文件中添加 public_key_pinning 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  public_key_pinning: ^1.0.0  # 请使用最新版本

然后运行 flutter pub get 来安装插件。

使用插件

以下是如何使用 public_key_pinning 插件的基本步骤:

  1. 导入插件

    import 'package:public_key_pinning/public_key_pinning.dart';
    
  2. 初始化公钥固定

    在应用程序启动时,初始化公钥固定。你需要提供服务器的公钥哈希值(通常是 SHA-256 哈希值)。

    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
    
      // 初始化公钥固定
      await PublicKeyPinning.init(
        sha256: 'your_sha256_hash_here',  // 替换为你的公钥 SHA-256 哈希值
        httpClient: HttpClient(),  // 使用自定义的 HttpClient
      );
    
      runApp(MyApp());
    }
    
  3. 发送请求

    使用 PublicKeyPinning 提供的 httpClient 来发送请求。这个 httpClient 会自动验证服务器的公钥。

    Future<void> fetchData() async {
      final httpClient = PublicKeyPinning.httpClient;
    
      final request = await httpClient.getUrl(Uri.parse('https://your-api-endpoint.com'));
      final response = await request.close();
    
      if (response.statusCode == 200) {
        final responseData = await response.transform(utf8.decoder).join();
        print('Response: $responseData');
      } else {
        print('Failed to load data: ${response.statusCode}');
      }
    }
    
  4. 处理异常

    如果服务器的公钥不匹配,PublicKeyPinning 会抛出异常。你可以捕获并处理这些异常。

    try {
      await fetchData();
    } on PublicKeyPinningException catch (e) {
      print('Public key pinning failed: ${e.message}');
    } catch (e) {
      print('An error occurred: $e');
    }
    

获取公钥哈希值

要获取服务器的公钥哈希值,你可以使用以下命令:

openssl s_client -connect your-api-endpoint.com:443 < /dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
回到顶部