Flutter生物识别安全存储插件secure_biometric_storage的使用

Flutter生物识别安全存储插件secure_biometric_storage的使用

简介

secure_biometric_storage 是一个基于 biometric_storage 的插件,专注于提高安全性。它是一个加密文件存储工具,可选地通过生物识别(如指纹或面部识别)进行保护,适用于 Android 和 iOS 平台。

该插件的主要用途是安全地存储小量数据,例如密码、密钥等,但不适合存储大量数据。其加密机制类似于 flutter_secure_storage

  • Android: 使用 androidx 和 KeyStore。
  • iOS: 使用 LocalAuthentication 和 KeyChain。

安全增强

  1. 在 Android 和 iOS 上,如果新增了指纹或面部识别,密钥将被自动失效。
  2. 使用“一次性认证”密钥来保护受生物识别锁保护的存储。
  3. 在 Android 上使用 BIOMETRIC_STRONG 认证。

“一次性认证”密钥要求用户每次访问由该密钥保护的数据时都需要提供生物识别凭证。这种密钥在高价值交易中非常有用,比如大额支付或更新健康记录。


开始使用

Android

要求:
  1. API Level >= 23
  2. MainActivity 必须继承自 FlutterFragmentActivity
  3. 主活动的主题必须使用 Theme.AppCompat 主题。否则,在 Android 29 以下版本上会导致崩溃。

示例配置:

AndroidManifest.xml

<activity
    android:name=".MainActivity"
    android:launchMode="singleTop"
    android:theme="@style/LaunchTheme"
/>

styles.xml

<style name="LaunchTheme" parent="Theme.AppCompat.NoActionBar">
    <!-- 显示启动屏 -->
    <item name="android:windowBackground">@drawable/launch_background</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowActionBar">false</item>
    <item name="android:windowFullscreen">true</item>
    <item name="android:windowContentOverlay">@null</item>
</style>

iOS

要求:
  1. 至少需要 iOS 9。
  2. Info.plist 文件中包含 NSFaceIDUsageDescription 键。

示例配置:

Info.plist 中添加以下内容:

<key>NSFaceIDUsageDescription</key>
<string>我们需要您的生物识别权限来验证身份。</string>

完整示例代码

以下是一个完整的示例代码,展示了如何使用 secure_biometric_storage 插件。

import 'package:flutter/material.dart';
import 'package:logging/logger.dart';
import 'package:logging_appenders/logging_appenders.dart';
import 'package:secure_biometric_storage/secure_biometric_storage.dart';

final MemoryAppender logMessages = MemoryAppender();

final _logger = Logger('main');

void main() {
  Logger.root.level = Level.ALL;
  PrintAppender().attachToLogger(Logger.root);
  logMessages.attachToLogger(Logger.root);
  _logger.fine('Application launched. (v2)');
  runApp(MyApp());
}

class StringBufferWrapper with ChangeNotifier {
  final StringBuffer _buffer = StringBuffer();

  void writeln(String line) {
    _buffer.writeln(line);
    notifyListeners();
  }

  [@override](/user/override)
  String toString() => _buffer.toString();
}

class ShortFormatter extends LogRecordFormatter {
  [@override](/user/override)
  StringBuffer formatToStringBuffer(LogRecord rec, StringBuffer sb) {
    sb.write('${rec.time.hour}:${rec.time.minute}:${rec.time.second} ${rec.level.name} '
        '${rec.message}');
    if (rec.error != null) {
      sb.write(rec.error);
    }
    final stackTrace = rec.stackTrace ?? (rec.error is Error ? (rec.error as Error).stackTrace : null);
    if (stackTrace != null) {
      sb.write(stackTrace);
    }
    return sb;
  }
}

class MemoryAppender extends BaseLogAppender {
  MemoryAppender() : super(ShortFormatter());

  final StringBufferWrapper log = StringBufferWrapper();

  [@override](/user/override)
  void handle(LogRecord record) {
    log.writeln(formatter.format(record));
  }
}

class MyApp extends StatefulWidget {
  [@override](/user/override)
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final String baseName = 'default';
  SecureBiometricStorageFile _authStorage;
  SecureBiometricStorageFile _storage;
  SecureBiometricStorageFile _customPrompt;
  SecureBiometricStorageFile _noConfirmation;
  List<BiometricType> _availableBiometrics;

  final TextEditingController _writeController = TextEditingController(text: 'Lorem Ipsum');

  [@override](/user/override)
  void initState() {
    super.initState();
    logMessages.log.addListener(_logChanged);
    _checkAuthenticate();
  }

  [@override](/user/override)
  void dispose() {
    logMessages.log.removeListener(_logChanged);
    super.dispose();
  }

  Future<CanAuthenticateResponse> _checkAuthenticate() async {
    final response = await SecureBiometricStorage().canAuthenticate();
    _logger.info('checked if authentication was possible: $response');
    return response;
  }

  Icon _mapBiometricType(BiometricType type) {
    IconData icon;
    switch (type) {
      case BiometricType.fingerprint:
        icon = Icons.fingerprint;
        break;
      case BiometricType.face:
        icon = Icons.face;
        break;
      case BiometricType.iris:
        icon = Icons.visibility;
        break;
      default:
        icon = Icons.device_unknown;
    }
    return Icon(icon);
  }

  void _logChanged() => setState(() {});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Column(
          children: [
            const Text('Methods:'),
            ElevatedButton(
              onPressed: () async {
                final availableBiometrics = await SecureBiometricStorage().getAvailableBiometrics();
                setState(() {
                  _availableBiometrics = availableBiometrics;
                });
              },
              child: const Text('getAvailableBiometrics'),
            ),
            ...?(_availableBiometrics == null
                ? null
                : [
                    const Text('Available Hardware', style: TextStyle(fontWeight: FontWeight.bold)),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceAround,
                      children: [..._availableBiometrics.map((e) => _mapBiometricType(e)).toList()],
                    ),
                  ]),
            const Divider(),
            ElevatedButton(
              child: const Text('init'),
              onPressed: () async {
                _logger.finer('Initializing $baseName');
                final authenticate = await _checkAuthenticate();
                bool supportsAuthenticated = false;
                if (authenticate == CanAuthenticateResponse.success) {
                  supportsAuthenticated = true;
                } else if (authenticate != CanAuthenticateResponse.unsupported) {
                  supportsAuthenticated = false;
                } else {
                  _logger.severe('Unable to use authenticate. Unable to get storage.');
                  return;
                }
                if (supportsAuthenticated) {
                  _authStorage = await SecureBiometricStorage().getStorage('${baseName}_authenticated',
                      options: StorageFileInitOptions(authenticationRequired: true));
                }
                _storage = await SecureBiometricStorage().getStorage('${baseName}_unauthenticated',
                    options: StorageFileInitOptions(
                      authenticationRequired: false,
                    ));
                if (supportsAuthenticated) {
                  _customPrompt = await SecureBiometricStorage().getStorage('${baseName}_customPrompt',
                      options: StorageFileInitOptions(authenticationRequired: true),
                      androidPromptInfo: const AndroidPromptInfo(
                        title: 'Custom title',
                        subtitle: 'Custom subtitle',
                        description: 'Custom description',
                        negativeButton: 'Nope!',
                      ));
                  _noConfirmation = await SecureBiometricStorage().getStorage('${baseName}_customPrompt',
                      options: StorageFileInitOptions(authenticationRequired: true),
                      androidPromptInfo: const AndroidPromptInfo(
                        confirmationRequired: false,
                      ));
                }
                setState(() {});
                _logger.info('initialized $baseName');
              },
            ),
            ...(_authStorage == null
                ? []
                : [
                    const Text('Biometric Authentication', style: TextStyle(fontWeight: FontWeight.bold)),
                    StorageActions(storageFile: _authStorage, writeController: _writeController),
                    const Divider(),
                  ]),
            ...?(_storage == null
                ? null
                : [
                    const Text('Unauthenticated', style: TextStyle(fontWeight: FontWeight.bold)),
                    StorageActions(storageFile: _storage, writeController: _writeController),
                    const Divider(),
                  ]),
            ...?(_customPrompt == null
                ? null
                : [
                    const Text('Custom Authentication Prompt (Android)', style: TextStyle(fontWeight: FontWeight.bold)),
                    StorageActions(storageFile: _customPrompt, writeController: _writeController),
                    const Divider(),
                  ]),
            ...?(_noConfirmation == null
                ? null
                : [
                    const Text('No Confirmation Prompt (Android)', style: TextStyle(fontWeight: FontWeight.bold)),
                    StorageActions(storageFile: _noConfirmation, writeController: _writeController),
                  ]),
            const Divider(),
            ElevatedButton(
              child: const Text('deleteAll'),
              onPressed: () async {
                _logger.info('deleting all data...');
                await SecureBiometricStorage().deleteAll();
              },
            ),
            const Divider(),
            TextField(
              decoration: const InputDecoration(
                labelText: 'Example text to write',
              ),
              controller: _writeController,
            ),
            Expanded(
              child: Container(
                color: Colors.white,
                constraints: const BoxConstraints.expand(),
                child: SingleChildScrollView(
                  child: Container(
                    padding: const EdgeInsets.all(16),
                    child: Text(
                      logMessages.log.toString(),
                    ),
                  ),
                  reverse: true,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class StorageActions extends StatelessWidget {
  const StorageActions({Key key, [@required](/user/required) this.storageFile, [@required](/user/required) this.writeController}) : super(key: key);

  final SecureBiometricStorageFile storageFile;
  final TextEditingController writeController;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: [
        ElevatedButton(
          child: const Text('read'),
          onPressed: () async {
            _logger.fine('reading from ${storageFile.name}');
            final result = await storageFile.read();
            _logger.fine('read: {$result}');
          },
        ),
        ElevatedButton(
          child: const Text('write'),
          onPressed: () async {
            _logger.fine('Going to write...');
            await storageFile.write(' [${DateTime.now()}] ${writeController.text}');
            _logger.info('Written content.');
          },
        ),
        ElevatedButton(
          child: const Text('delete'),
          onPressed: () async {
            _logger.fine('deleting...');
            await storageFile.delete();
            _logger.info('Deleted.');
          },
        ),
      ],
    );
  }
}

更多关于Flutter生物识别安全存储插件secure_biometric_storage的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter生物识别安全存储插件secure_biometric_storage的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


secure_biometric_storage 是一个 Flutter 插件,用于在设备上安全地存储数据,并且可以通过生物识别(如指纹、面部识别等)来保护这些数据。它结合了生物识别和加密技术,确保数据的安全性。

安装插件

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

dependencies:
  flutter:
    sdk: flutter
  secure_biometric_storage: ^0.0.1

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

使用插件

1. 初始化插件

在使用插件之前,你需要初始化它。通常,你可以在 main.dart 文件中进行初始化:

import 'package:secure_biometric_storage/secure_biometric_storage.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await SecureBiometricStorage.init();
  runApp(MyApp());
}

2. 存储数据

你可以使用 SecureBiometricStorage 来存储数据。以下是一个简单的示例:

import 'package:secure_biometric_storage/secure_biometric_storage.dart';

Future<void> storeData() async {
  final storage = SecureBiometricStorage();
  await storage.write(key: 'my_key', value: 'my_secret_data');
}

3. 读取数据

要读取存储的数据,你可以使用以下代码:

import 'package:secure_biometric_storage/secure_biometric_storage.dart';

Future<void> readData() async {
  final storage = SecureBiometricStorage();
  final value = await storage.read(key: 'my_key');
  print('Stored value: $value');
}

4. 删除数据

如果你想要删除存储的数据,可以使用以下代码:

import 'package:secure_biometric_storage/secure_biometric_storage.dart';

Future<void> deleteData() async {
  final storage = SecureBiometricStorage();
  await storage.delete(key: 'my_key');
}

5. 检查生物识别是否可用

在使用生物识别之前,你可以检查设备是否支持生物识别:

import 'package:secure_biometric_storage/secure_biometric_storage.dart';

Future<void> checkBiometrics() async {
  final storage = SecureBiometricStorage();
  final isAvailable = await storage.canAuthenticate();
  print('Biometrics available: $isAvailable');
}

注意事项

  1. 权限:在 Android 上,你需要在 AndroidManifest.xml 中添加以下权限:

    <uses-permission android:name="android.permission.USE_BIOMETRIC" />
    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
    
  2. iOS 配置:在 iOS 上,你需要在 Info.plist 中添加以下配置:

    <key>NSFaceIDUsageDescription</key>
    <string>We need to use Face ID to secure your data.</string>
    
  3. 错误处理:在使用插件时,确保处理可能出现的错误,例如生物识别失败或设备不支持生物识别。

示例代码

以下是一个完整的示例,展示了如何使用 secure_biometric_storage 插件来存储、读取和删除数据:

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

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await SecureBiometricStorage.init();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Secure Biometric Storage Example')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                onPressed: storeData,
                child: Text('Store Data'),
              ),
              ElevatedButton(
                onPressed: readData,
                child: Text('Read Data'),
              ),
              ElevatedButton(
                onPressed: deleteData,
                child: Text('Delete Data'),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Future<void> storeData() async {
    final storage = SecureBiometricStorage();
    await storage.write(key: 'my_key', value: 'my_secret_data');
    print('Data stored successfully');
  }

  Future<void> readData() async {
    final storage = SecureBiometricStorage();
    final value = await storage.read(key: 'my_key');
    print('Stored value: $value');
  }

  Future<void> deleteData() async {
    final storage = SecureBiometricStorage();
    await storage.delete(key: 'my_key');
    print('Data deleted successfully');
  }
}
回到顶部