Flutter云端验证插件cloudproof的使用

Flutter云端验证插件cloudproof的使用

Cloudproof Flutter Library

workflow

Cloudproof Flutter 库为 Flutter 提供了一个友好的 API,用于访问 Cosmian Cloudproof 加密产品

简而言之,Cloudproof 加密产品通过基于属性的访问控制加密和加密搜索来保护云中的数据存储库。

目录


许可证

该库采用双重许可方案:Affero GPL/v3 和商业许可。详情请参阅 LICENSE.md


密码学原语

该库基于以下组件:

  • CoverCrypt 算法允许创建一组属性的密文并发放用户密钥以访问这些属性的策略。它具有后量子抗性。
  • Findex 是一种加密协议,设计用于在不受信任的云服务器上安全地进行搜索查询。通过其加密索引,可以安全地外包大型数据库而不影响可用性。
  • FPE 提供了格式保留加密技术,适用于零信任环境。这些技术基于 NIST:800-38G 描述的 FPE-FF1。
  • 匿名化:数据匿名化是指通过转换数据使其无法再识别个人的过程,除非使用额外的信息。这通常用于保护所收集或处理的数据中个人的隐私。
  • KMS:此存储库提供了对称和非对称密钥生命周期管理的 Key Management Services(服务器+客户端)实现。

Cosmian 库功能概述

下表展示了当前 Cosmian 密码学原语的支持情况:

功能 CoverCrypt Findex KMS 客户端 FPE 匿名化
cloudproof_rust
cloudproof_java
cloudproof_python
cloudproof_js
cloudproof_flutter

入门指南

CoverCrypt

CoverCrypt 允许解密之前使用我们的库(Java、Python、Rust 等)加密的数据。

要查看如何使用 CoverCrypt 的示例,请查阅 test/cover_crypt/cover_crypt_test.dart

Findex

Findex 允许对加密索引执行加密搜索查询。要使用 Findex,您需要一个能够存储和更新索引的驱动程序(例如 SQLite、Redis 或其他存储方法)。您可以在 test/findex_redis_test.darttest/findex_sqlite_test.dart 中找到两个实现示例。

搜索
  1. 复制粘贴以下代码。
  2. <code>TODO_ReplaceThisByTheNameOfYourClassOrTheRawFunction</code> 替换为您类的名称。
  3. 实现 fetchEntriesfetchChains 方法。
static List<UidAndValue> fetchEntries(Uids uids) async {
  // Implement me!
}

static List<UidAndValue> fetchChains(Uids uids) async {
  // Implement me!
}

// --------------------------------------------------
// Copy-paste code :AutoGeneratedImplementation
// --------------------------------------------------
static Future<Map<Keyword, Set<Location>>> search(Set<Keyword> keywords,
  {int findexHandle = -1}) async {
return await Findex.search(keywords, findexHandle: findexHandle);
}

static int fetchEntriesCallback(
  Pointer<Uint8> outputEntryTableLinesPointer,
  Pointer<Uint32> outputEntryTableLinesLength,
  Pointer<Uint8> uidsPointer,
  int uidsNumber,
) {
  return Findex.wrapSyncFetchCallback(
    TODO_ReplaceThisByTheNameOfYourClassOrTheRawFunction.fetchEntries,
    outputEntryTableLinesPointer,
    outputEntryTableLinesLength,
    uidsPointer,
    uidsNumber,
  );
}

static int fetchChainsCallback(
  Pointer<Uint8> outputChainTableLinesPointer,
  Pointer<Uint32> outputChainTableLinesLength,
  Pointer<Uint8> uidsPointer,
  int uidsNumber,
) {
  return Findex.wrapSyncFetchCallback(
    TODO_ReplaceThisByTheNameOfYourClassOrTheRawFunction.fetchChains,
    outputChainTableLinesPointer,
    outputChainTableLinesLength,
    uidsPointer,
    uidsNumber,
  );
}
增量插入
  1. 复制粘贴以下代码。
  2. <code>TODO_ReplaceThisByTheNameOfYourClassOrTheRawFunction</code> 替换为您类的名称。
  3. 实现 fetchEntriesupsertEntriesinsertChains 方法。
static List<UidAndValue> fetchEntries(Uids uids) async {
  // Implement me!
}

static List<UidAndValue> upsertEntries(UpsertData entries) {
  // Implement me!
}

static void insertChains(List<UidAndValue> chains) {
  // Implement me!
}

// --------------------------------------------------
// Copy-paste code :AutoGeneratedImplementation
// --------------------------------------------------

static Future<Set<Keyword>> add(Map<IndexedValue, Set<Keyword>> additions,
  {int findexHandle = -1}) async {
return Findex.add(additions, findexHandle: findexHandle);
}

static int upsertEntriesCallback(
  Pointer<Uint8> outputRejectedEntriesListPointer,
  Pointer<Uint32> outputRejectedEntriesListLength,
  Pointer<Uint8> oldValuesPointer,
  int oldValuesLength,
  Pointer<Uint8> newValuesPointer,
  int newValuesLength,
) {
  return Findex.wrapSyncUpsertEntriesCallback(
    TODO_ReplaceThisByTheNameOfYourClassOrTheRawFunction.upsertEntries,
    outputRejectedEntriesListPointer,
    outputRejectedEntriesListLength,
    oldValuesPointer,
    oldValuesLength,
    newValuesPointer,
    newValuesLength,
  );
}

static int insertChainsCallback(
  Pointer<Uint8> chainsListPointer,
  int chainsListLength,
) {
  return Findex.wrapSyncInsertChainsCallback(
    TODO_ReplaceThisByTheNameOfYourClassOrTheRawFunction.insertChains,
    chainsListPointer,
    chainsListLength,
  );
}

注意:如果您同时调用 searchadd,这两个实现可以共享相同的 fetchEntries 回调。

注意:如果您的回调是异步的,您可以使用 Findex.wrapAsyncFetchCallbackwrapAsyncUpsertEntriesCallbackwrapAsyncInsertChainsCallback

注意:当 Dart 实现 https://github.com/dart-lang/language/issues/1482 后,复制粘贴代码可能会被移除。


安装

flutter pub get cloudproof

下载所需的本机库

Cloudproof Flutter 库使用 FFI 来访问以下本机加密库的功能:

  • CoverCrypt
  • Findex

这些库必须位于 Android、iOS 和 PC 架构的特定子文件夹中。

  • Android: 在 android/src/main/jniLibs/ 子文件夹中:
    • arm64-v8a
    • armeabi-v7a
    • x86
    • x86_64
  • iOS: 在 ios/ 文件夹中
  • PC: 在 resources/ 文件夹中

要下载它们,请运行以下脚本,从公共 URL https://package.cosmian.com 获取发布版本:

flutter pub get
python3 scripts/get_native_libraries.py

否则,要手动构建这些库,请检查 CoverCrypt 和 Findex 项目在 GitHub 上的文档,其 build 目录包含有关如何为您的系统构建本机库的说明。


示例

要运行示例,您需要配置 Redis 服务器。然后,在 example/lib/findex_redis_implementation.dart 文件顶部更新 redisHostredisPort


测试

要运行所有测试:

flutter test

某些测试需要本地 Redis 数据库(默认端口)。

您可以通过以下命令运行基准测试:

dart benchmark/cloudproof_benchmark.dart

警告

  • fetchEntriesfetchChainsupsertEntriesinsertChains 不能是类中的静态方法或原始函数,但应该是静态的!您不能在这里放置实例的经典方法。
  • fetchEntriesfetchChainsupsertEntriesinsertChains(如果是异步的)不能访问程序的状态,它们将在单独的 Isolate 中运行,与主线程没有数据交互(例如,在程序初始化阶段填充的静态/全局变量将不存在)。如果您需要从主线程访问某些数据,唯一的方法是我们认为可行的是将这些信息保存到文件或数据库中,并从回调中读取。这种模式会减慢 search 过程。如果您不需要回调中的异步操作(例如,sqlite 库有同步函数,您可以调用 <code>*WrapperWithoutIsolate</code> 并保持所有过程在同一线程中,因此您可以使用全局变量)。

实现细节

  • searchupsert 方法将通过本机绑定同步调用 Rust FFI。如果您不想阻塞主线程,请调用 compute 以在不同的 Isolate 中运行搜索。

FFI 库说明

该项目最初通过以下方式创建:

flutter create --org com.example --template=plugin --platforms=android,ios -a kotlin cloudproof

cloudproof.h 生成 Dart 绑定

lib/src/generated_bindings.dart 是通过 ffigen 使用 ./ffigen_*.yml 配置文件生成的:

flutter pub run ffigen --config ffigen_cloudproof.yaml
iOS 警告

使用 cbindgen,请勿忘记从 libcloudproof_cover_crypt.h 中删除 <code>str</code> 类型(最后两行),以便在 iOS 上编译(C 头文件中未知类型)。

这两个 .h 文件需要放在 ios/Classes 文件夹中。Android 不需要 .h 文件。

构建 .so.a

Linux

只需从 Rust 项目中复制 .so 文件到 resources 文件夹中。这些 .so 文件仅用于在 Linux 上运行测试。

Android

从 Gitlab CI 下载工件。您应该得到一个 jniLibs 文件夹,将其复制到 android/src/main

然后运行以下命令:

cd example
flutter pub get
flutter run
iOS

如果在 Linux 上使用 cargo lipo 构建,我们只能得到 <code>aarch64-apple-ios</code><code>x86_64-apple-ios</code>

codemagic.io 上:

  • <code>aarch64-apple-ios</code> 失败,错误为:“ld: in /Users/builder/clone/ios/libcloudproof_cover_crypt.a(cover_crypt.cover_crypt.aea4b2d2-cgu.0.rcgu.o),为 iOS 模拟器构建,但尝试链接使用 iOS 构建的对象文件,文件 ‘/Users/builder/clone/ios/libcloudproof_cover_crypt.a’ 对于架构 arm64”
  • <code>x86_64-apple-ios</code> 失败,错误为:“ld: 警告:忽略文件 /Users/builder/clone/ios/libcloudproof_cover_crypt.a,为 iOS 模拟器-arm64 构建,但尝试链接使用 iOS 模拟器-x86_64 构建的文件”

为了使 flutter 构建成功,需要以下三个先决条件:

  • 在 CloudproofPlugin.h 中声明头文件(CoverCrypt 和 Findex)
  • 在 SwiftCloudproofPlugin.swift 中人工调用每个本机库的 1 个函数
  • 使用通用 iOS 构建:将两个 .a 文件复制到 <code>cloudproof_flutter/ios</code>

支持的版本

Linux

系统 Flutter Dart Android SDK NDK Glibc LLVM 智能手机虚拟设备
Ubuntu 22.04 3.3.4 2.18.2 Chipmunk 2021.2.1 r25 2.35 14.0.0-1 Pixel 5 API 30
Centos 7 3.3.4 2.18.2 Chipmunk 2021.2.1 r25 2.17 - -

macOS

系统 Flutter Dart 操作系统 LLVM Xcode 智能手机虚拟设备
Catalina 3.3.4 2.18.2 Catalina 12.0.0 - iPhone 12 PRO MAX
通过 snap 安装降级 Flutter 版本
cd ~/snap/flutter/common/flutter/
git checkout 3.3.4

Cloudproof 版本对应关系

当使用本地加密和解密时,需要使用 CoverCrypt 本机库。

请检查相应项目的主页以构建适合您系统的本机库。<code>resources</code> 目录提供了适用于 Linux GLIBC 2.17 的预构建库。这些库应该可以在具有较新 GLIBC 版本的系统上正常运行。

下表显示了各种组件之间的最低版本对应关系:

Flutter Lib CoverCrypt lib Findex
0.1.0 6.0.5 0.7.2
1.0.0 6.0.5 0.7.2
2.0.0 7.1.0 0.10.0
3.0.0 8.0.0 0.12.0
4.0.0 8.0.0 1.0.1
4.0.1,4.0.2,4.0.3 8.0.0,8.0.1,8.0.2 2.0.0
5.0.0 10.0.0 2.0.1
5.1.0 10.0.0 2.1.0

从版本 6.0.0 开始,cloudproof_flutter 依赖于 <code>cloudproof_rust</code>,它封装了 <code>CoverCrypt</code><code>Findex</code> 的接口。

Flutter Lib Cloudproof Rust lib
6.0.0 1.0.0
6.0.2 1.0.1
7.0.0 2.0.1
8.0.0 2.4.0
8.1.0 3.0.0

示例代码

以下是完整的示例代码,展示了如何使用 Cloudproof 插件:

import 'dart:async';
import 'dart:convert';
import 'dart:developer';

import 'package:cloudproof/cloudproof.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import 'cover_crypt_helper.dart';
import 'redis_findex.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Cloudproof Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Cloudproof Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
  bool loading = true;

  late Uint8List key;
  late Uint8List label;
  late CoverCryptHelper coverCryptHelper;

  String? error;

  List<String?> results = [];
  Duration? searchDuration;
  Duration? decryptDuration;

  late Debouncer _debouncer;
  late AnimationController controller;

  _MyHomePageState() {
    _debouncer = Debouncer(milliseconds: 500);
  }

  [@override](/user/override)
  void initState() {
    controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 5),
    )..addListener(() {
        setState(() {});
      });
    coverCryptHelper = CoverCryptHelper();
    indexDataForDemo();
    super.initState();
  }

  void indexDataForDemo() async {
    try {
      key = Uint8List(16);
      await FindexRedisImplementation.init(coverCryptHelper, key, "NewLabel");
      await FindexRedisImplementation.indexAll();

      setState(() => loading = false);

      log("Initialized");
    } catch (e) {
      setState(() => error = "Problem during indexation $e");
      log("Problem during indexation $e");
    }
  }

  [@override](/user/override)
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  List<Uint8List> getAllLocations(Iterable<Set<Location>> searchResult) {
    List<Uint8List> res = [];
    for (final locations in searchResult) {
      for (final loc in locations) {
        res.add(loc.bytes);
      }
    }
    return res;
  }

  void setQuery(String query) {
    log(query);

    _debouncer.run(() async {
      try {
        final stopwatch = Stopwatch()..start();
        final searchResult =
            await FindexRedisImplementation.search({Keyword.fromString(query)});

        final newSearchDuration = stopwatch.elapsed;

        if (searchResult.isEmpty) {
          setState(() {
            results = [];
            searchDuration = null;
            decryptDuration = null;
            error = "No result";
          });
          return;
        }

        final encryptedUsersFromRedis = (await FindexRedisImplementation.mget(
                await FindexRedisImplementation.db,
                RedisTable.users,
                getAllLocations(searchResult.values)))
            // Remove `null` if some location doesn't exists inside Redis
            .whereType<List<int>>()
            .map(Uint8List.fromList)
            .toList();

        stopwatch.reset();
        final plaintextUsersBytes = encryptedUsersFromRedis.map((userBytes) {
          // Try to decrypt user information.
          try {
            return CoverCrypt.decrypt(
                coverCryptHelper.userSecretKey, userBytes);
          } catch (e) {
            return null;
          }
        }).toList();
        final newDecryptDuration = stopwatch.elapsed;

        final plaintextUsers = plaintextUsersBytes
            .map((e) => e == null ? null : utf8.decode(e.plaintext))
            .toList();

        setState(() {
          results = plaintextUsers;
          searchDuration = newSearchDuration;
          decryptDuration = newDecryptDuration;
          error = "";
        });
      } catch (e, stacktrace) {
        setState(() => error = "Exception during search $e $stacktrace");
        log("Exception during search $e $stacktrace");
      }
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            if (loading)
              CircularProgressIndicator(
                value: controller.value,
                semanticsLabel: 'Circular progress indicator',
              ),
            if (!loading)
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16),
                child: TextField(
                  onChanged: (text) {
                    setQuery(text);
                  },
                  decoration: const InputDecoration(
                    border: OutlineInputBorder(),
                    hintText: 'Enter a search term',
                  ),
                ),
              ),
            if (error != null) Text(error as String),
            if (!loading && searchDuration != null && decryptDuration != null)
              Text(
                "${results.length} results. Search took ${searchDuration!.inMilliseconds}ms. Decrypt took ${decryptDuration!.inMilliseconds}ms",
              ),
            if (!loading)
              Expanded(
                child: ListView.separated(
                  padding: const EdgeInsets.all(8),
                  separatorBuilder: (context, index) => const Divider(
                    color: Colors.black,
                  ),
                  itemCount: results.length,
                  itemBuilder: (context, index) {
                    return Text(results[index] ??
                        "Impossible to decrypt this data with current permissions");
                  },
                ),
              ),
          ],
        ),
      ),
    );
  }
}

class Debouncer {
  final int milliseconds;
  Timer? _timer;

  Debouncer({required this.milliseconds});

  run(VoidCallback action) {
    _timer?.cancel();
    _timer = Timer(Duration(milliseconds: milliseconds), action);
  }
}

更多关于Flutter云端验证插件cloudproof的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter云端验证插件cloudproof的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


cloudproof 是一个用于在 Flutter 应用中实现云端验证的插件。它可以帮助开发者轻松地集成云端验证功能,确保应用的安全性和数据的完整性。以下是如何在 Flutter 项目中使用 cloudproof 插件的基本步骤:

1. 添加依赖

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

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

然后运行 flutter pub get 来获取依赖。

2. 导入插件

在你的 Dart 文件中导入 cloudproof 插件。

import 'package:cloudproof/cloudproof.dart';

3. 初始化插件

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

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Cloudproof.initialize();
  runApp(MyApp());
}

4. 使用云端验证功能

cloudproof 提供了多种云端验证功能,例如验证用户身份、验证数据完整性等。以下是一个简单的示例,展示如何使用 cloudproof 进行用户身份验证。

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Cloudproof Example'),
        ),
        body: Center(
          child: ElevatedButton(
            onPressed: () async {
              try {
                bool isVerified = await Cloudproof.verifyUserIdentity('user_id', 'token');
                if (isVerified) {
                  print('User identity verified successfully.');
                } else {
                  print('User identity verification failed.');
                }
              } catch (e) {
                print('Error during verification: $e');
              }
            },
            child: Text('Verify User Identity'),
          ),
        ),
      ),
    );
  }
}
回到顶部