开发工具: 华为云码道

DevEco Studio: 26.0.0(26.0.0.851)

原始仓库: iktakahiro/fargon2(GitHub)

本文配套仓库: oh-flutter/fargon2(AtomGit)

fargon2 把 Argon2 口令哈希封装成一个跨平台调用:Dart 侧只需要给出模式与参数,Android 通过 argon2kt 调用参考 C 实现,iOS 通过 CatCrypto 调用系统外的 Argon2 实现。本文以 fargon2 1.0.1 为例,介绍源码准备、OHOS 平台实现、Example 宿主工程配置和真机运行验证。

在这里插入图片描述

这个库的适配比一般插件重得多:OpenHarmony 的系统密码学框架没有 Argon2,因此适配不是「调一个系统 API 再包一层」,而是在 ArkTS 侧把 BLAKE2b 与 Argon2d / Argon2i / Argon2id 完整实现出来。Dart 层保持零改动,通道名、方法名、入参键名与返回值类型与 Android / iOS 完全一致。文中的代码以提交 6be4e42030b88bae197b4726fad4885de32fda77 为参考,对应 TAG 1.0.1-ohos-1.0.0-beta.1;文中所有截图均为真机实拍。

在这里插入图片描述


一、插件简介与适配目标

Argon2 是 2015 年密码哈希竞赛(PHC)的获胜算法,也是目前口令存储场景的推荐选择。它把「时间代价」「内存代价」「并行度」三个维度都做成参数,让攻击者既不能靠算力硬堆、也不能靠显卡并行绕开。三个变体的差异在于索引计算如何依赖内存内容:

变体索引计算适用场景
argon2d依赖内存块内容抗 GPU 最强,但存在侧信道风险,不推荐用于口令
argon2i不依赖内存内容抗侧信道,适合不可信执行环境
argon2id前半段 argon2i、后半段 argon2d兼顾两者,当前推荐默认

fargon2 对外只暴露一个方法:

Fargon2(mode: Fargon2Mode.argon2id).hash(
  passphrase: String,
  salt: String,
  hashLength: int = 32,
  iterations: int = 3,
  parallelism: int = 2,
  memoryKibibytes: int = 65536,
) -> Future<String>

KeyHash 示例页 KeyHash 示例页 KeyHash 示例页 KeyHash 示例页 KeyHash 示例页 KeyHash 示例页 KeyHash 示例页 KeyHash 示例页

返回的是 raw hash 字节的 Base64 字符串(不是 PHC 那种 $argon2id$v=19$m=... 的编码串),Dart 层还会把换行去掉。这个返回值契约是适配时必须逐字节对齐的关键:Android 与 iOS 都返回 Base64,OHOS 侧如果返回 Hex 或 PHC 串,业务侧的解码代码就会跨平台不一致。

上游的原生实现依赖较重:android/ 使用 argon2kt(JNI 调用参考 C 实现),ios/ 使用 CatCrypto(Swift 侧内置 Argon2)。而 OpenHarmony 的系统密码学框架 @ohos.security.cryptoFramework 只提供 SHA / MD5 / SM3 / HMAC / PBKDF2 / SCRYPT / HKDF / X963KDF——不包含 Argon2。所以本次适配要做的是:

  1. 在 pubspec.yaml 里声明 ohos 平台,让工具链生成注册代码;
  2. 在 ArkTS 侧实现 BLAKE2b 与 Argon2 三种变体,让同名通道 fargon2 的同名方法 hash 返回与 Android / iOS 完全一致的 Base64;
  3. 生成可编译、可运行、可验证的 example/ohos 宿主工程,在真机上跑通三种模式、参数调整、重复计算一致性与性能边界。

二、环境准备

环境搭建参考社区文档:Flutter OH 开发环境搭建,完成 Flutter OH SDK 安装、环境变量和 DevEco Studio 配置。

完成后,在宿主机终端执行以下命令,确认当前选中的是支持 OHOS 的 Flutter 工具链,并能发现目标设备:

flutter --version
flutter doctor -v
hdc list targets

在这里插入图片描述
在这里插入图片描述

编辑用户

工程使用的工具链和 SDK 配置如下:

项目版本或配置用途
Flutter OHOS SDK3.47.7-ohos-1.0.0Flutter 编译与 OHOS 平台工具链
Flutter 分支oh-3.47-dev对应开发分支(framework revision 4b5928c43f)
Dart SDK3.13.3Dart 语言与包管理环境
DevEco Studio26.0.0(26.0.0.851)OHOS 应用工程开发套件(build 261.23567.138.36.2600851)
compatibleSdkVersion6.1.1(24)当前工程声明的最低兼容版本
targetSdkVersion6.1.1(24)应用面向的行为版本
测试设备TLR-AL00(nova 14),OpenHarmony-6.1.1.120(API 24),arm64-v8a真机验证环境
库版本1.0.1pubspec.yaml 中的包版本
原生语言ArkTSohos/ 模块实现语言
插件产物HAR(ohos/)经 GeneratedPluginRegistrant.ets 自动注册

2.1 开发套件版本与工程中的 SDK 版本配置

6.1.1(24) 和工程配置涉及 OpenHarmony 版本号体系。在本文工程中,相关版本的含义与配置方式如下:

  • 6.1.1(24) 采用「版本(API)」写法,表示 OpenHarmony 6.1.1 版本、对应 API 24;
  • compatibleSdkVersion 与 targetSdkVersion 均为 6.1.1(24):声明最低兼容 API 24,并以 API 24 为行为目标;
  • 本机 DevEco Studio 为 26.0.0(26.0.0.851),自带 SDK 为 API 26,高于工程声明值;编译按本机 SDK 执行、安装兼容性按工程声明判断,两者并不冲突;
  • 本次构建与真机验证都使用 DevEco Studio 26.0.0(26.0.0.851) 自带的 hvigorw 与 JBR。
    在这里插入图片描述

判断依据是设备的 hdc shell param get const.ohos.apiversion:本次测试机返回 24,const.ohos.fullname 返回 OpenHarmony-6.1.1.120,与工程声明一致,安装与运行的版本门槛都满足。


三、从源码仓库开始准备适配工程

3.1 将上游源码同步到 AtomGit

适配已有第三方库时,先从 Pub 包信息或项目 README 确认上游地址、包名、版本和许可证。同步仓库时保留源码、许可证和提交历史,方便后续跟踪上游更新。

在 AtomGit 网页的新建仓库流程中,找到从已有仓库导入的入口,填写上游 Git 地址,选择自己有写权限的组织或个人空间,设置目标仓库名称后执行导入。完成后检查目标分支、pubspec.yaml、LICENSE 和提交历史是否完整,并记录适配起点的提交号。如果上游本身托管在 AtomGit,也可以通过 Fork 获得自己的工作仓库。

本例的上游为 iktakahiro/fargon2,包名 fargon2,pubspec.yaml 中的版本为 1.0.1,许可证为 MIT(LICENSE 开头是 MIT License / Copyright (c) 2021 Takahiro Ikeuchi)。适配起点的提交是 2f9f0e8(Merge branch 'release/1.0.1',上游最后一个发布相关提交,时间为 2023-05-08)。

仓库名与 Dart 包名一致,都是 fargon2,因此同步到 AtomGit 时保持原名即可,不必加 fluttertpc_ 前缀。

fargon2 现可从 AtomGit 获取。下面使用 AtomGit 地址拉取代码;需要提交修改时,使用自己有写权限的仓库或 Fork。

3.2 将代码拉取到宿主机

在安装了 Flutter OH 和 DevEco Studio 的开发电脑上打开终端,进入准备存放项目的目录,执行:

git clone https://atomgit.com/oh-flutter/fargon2.git
cd fargon2
pwd
ls
git remote -v
git status --short --branch
git rev-parse HEAD

git clone 会创建 fargon2/ 目录;cd 后的位置就是下文所说的仓库根目录,这里应能看到 pubspec.yaml、lib/、ohos/、example/,以及上游保留的 android/、ios/ 和 docs/。

需要使用与本文相同的代码版本时,在没有未提交修改的仓库中切换到以下提交:

git switch --detach 6be4e42030b88bae197b4726fad4885de32fda77

适配其他插件时,使用该插件对应版本的 tag 或 commit 作为分支起点。

请添加图片描述

图 1:在宿主机终端输入 AtomGit 仓库拉取命令。

3.3 在仓库根目录确认分支与版本标记

本库没有为 OHOS 适配单独开特性分支,适配提交直接落在 main 上,版本通过 TAG 标记。TAG 命名规则为 原库版本-ohos-版本号-beta.x,本库使用 1.0.1-ohos-1.0.0-beta.1。在 fargon2/ 根目录执行:

git branch --show-current
git tag --list
git log --oneline -3
git status --short --branch

git branch --show-current 用于确认当前所在分支,git tag --list 用于确认版本标记是否已推送。向接收仓库提交时按其对分支与 TAG 的要求执行。

请添加图片描述

图 2:在 fargon2 仓库根目录确认分支与 TAG。

图 2 里 git tag --list 同时列出了上游的 v1.0.0、v1.0.1 与本次的 1.0.1-ohos-1.0.0-beta.1,三类标记并存是正常现象:前两者是上游的历史版本,后者是适配版本。需要注意当前 origin 仍指向上游 GitHub,适配提交只在本地 main(## main...origin/main [ahead 1]),推送目标要改成 oh-flutter/fargon2 后再执行推送(做法见 6.3)。

3.4 补全 ohos 平台与 example 宿主工程

上游已经按插件形式声明了 android 与 ios 平台,只是缺少 ohos。补全方式是在仓库根目录执行插件模板生成,让它同时产出 ohos/ 模块与 example/ohos/ 宿主工程:

flutter create . --template=plugin --platforms=ohos --org dev.iktakahiro
  • 参数 --template=plugin 表示按插件模板生成;对已经有 lib/、example/ 的仓库,它只补缺的目录,不覆盖已有实现;
  • --platforms=ohos 让它只生成 OHOS 相关文件;
  • --org dev.iktakahiro 决定宿主工程的 bundleName,本次生成的是 dev.iktakahiro.fargon2_example,与上游 Android 侧的 dev.iktakahiro.fargon2 包名前缀保持一致;
  • 最后的 . 表示在当前插件目录补全工程,不是另建一层 fargon2/。

该命令生成 OHOS 平台脚手架。生成后务必用 diff 检查:模板会连带注入与 OHOS 无关的内容,例如 federated 骨架(lib/*_method_channel.dart、lib/*_platform_interface.dart、lib/*_web.dart)、.kts 构建脚本、*.iml、local.properties,还可能改写 analysis_options.yaml。本次适配保留了已有 API、其他平台注册项及依赖配置,只保留 ohos/ 与 example/ohos/ 相关产物,并在 analysis_options.yaml 中补了 analyzer.exclude(build/**、android/**、ios/**),避免 analyze 扫到生成物与原生工程。

请添加图片描述

图 3:在仓库根目录执行插件模板生成,并确认 pubspec.yaml 新增了 ohos 声明。

pubspec.yaml 的改动很小,只有两行:

      ohos:
        pluginClass: Fargon2Plugin

pluginClass 必须与 ArkTS 侧类的 getUniqueClassName() 返回值一致,否则 Flutter 引擎在注册阶段会找不到插件类。

3.5 适配后的项目目录

fargon2/
├── lib/
│   └── fargon2.dart
├── ohos/
│   ├── index.ets
│   ├── oh-package.json5
│   ├── hvigorfile.ts
│   └── src/main/
│       ├── module.json5
│       └── ets/components/plugin/
│           ├── Fargon2Plugin.ets
│           ├── Argon2.ets
│           ├── Blake2b.ets
│           └── Bytes.ets
├── example/
│   ├── lib/main.dart
│   └── ohos/
├── test/
│   └── fargon2_test.dart
├── docs/ohos/
├── README.OpenHarmony_CN.md
├── CHANGELOG.OpenHarmony.md
└── pubspec.yaml

项目根目录如下,其中包含 ohos/、example/,以及 OpenHarmony 中英文说明和变更记录文件:

请添加图片描述

图 4:适配后的 fargon2 仓库目录结构。

各新增文件的职责:

文件职责
ohos/index.ets导出 Fargon2Plugin,作为 HAR 的入口
ohos/oh-package.json5HAR 名称 / 版本 / 许可证(name: fargon2、license: MIT)
ohos/src/main/module.json5模块声明(type: har,deviceTypes: default / tablet)
ohos/.../Fargon2Plugin.ets通道注册、参数校验、Base64 返回、hilog(209 行)
ohos/.../Argon2.etsArgon2d / i / id 核心:H0、填充阶段、索引计算、变长哈希 H’(486 行)
ohos/.../Blake2b.etsBLAKE2b,Argon2 的 H0 与 H’ 依赖它(218 行)
ohos/.../Bytes.etsUTF-8 编码、Base64、小端整数读写(95 行)
example/ohos/宿主工程:EntryAbility 加载 Flutter 引擎,GeneratedPluginRegistrant 注册插件
README.OpenHarmony_CN.md安装方式、TAG 对应表、约束与 API 说明
CHANGELOG.OpenHarmony.md适配版本变更记录与已知限制

四、Dart 实现分析

lib/fargon2.dart 全文 67 行,是整个库唯一的 Dart 文件。它不依赖任何平台判断,也不做能力探测——这一点决定了本次适配对 Dart 层的零改动。

结构非常简单:

  1. Fargon2Mode 枚举:argon2i / argon2d / argon2id,配一个 Fargon2ModeString 扩展把枚举转成小写字符串;
  2. Fargon2 类:持有 mode,静态常量 MethodChannel('fargon2');
  3. hash():组装参数、校验 salt 长度、invokeMethod('hash', params)、去掉返回值里的换行;
  4. Fargon2ArgumentError:Dart 层的参数异常类型。

4.1 跨端架构与调用时序

ohos/src/main/ets/components/plugin

原生实现

Dart 层(lib/fargon2.dart,67 行,零改动)

业务应用(Flutter)

Fargon2(mode).hash(...)

Fargon2Mode → 字符串

salt.length < 8 → Fargon2ArgumentError

MethodChannel('fargon2')

Android:argon2kt(参考 C 实现,JNI)

iOS:CatCrypto(Swift)

OpenHarmony:ArkTS 自实现

Fargon2Plugin.ets
通道 + 校验 + Base64

Argon2.ets
Argon2d / i / id

Blake2b.ets
H0 与 H'

Bytes.ets
UTF-8 / Base64 / 小端

三端共用同一条通道契约,差异只在「谁来算」:Android 与 iOS 交给各自的原生库,OHOS 交给 ArkTS 实现。

4.1.1 一次 hash 的完整时序
Argon2 + Blake2b Fargon2Plugin MethodChannel('fargon2') Fargon2.hash 业务代码 Argon2 + Blake2b Fargon2Plugin MethodChannel('fargon2') Fargon2.hash 业务代码 alt [参数非法] [参数合法] passphrase / salt / 参数 salt.length < 8 ? 抛 Fargon2ArgumentError invokeMethod('hash', 7 个参数) onMethodCall('hash', args) 取参 → 整数校验 → 范围校验 error(FARGON2_BAD_ARGS) PlatformException hash(type, UTF-8(password), UTF-8(salt), dkLen, t, m, p) H0 = BLAKE2b(参数块 + password + salt + ...) 填充 m 个 1 KiB 块(Argon2d / i / id 索引不同) 逐块压缩 → 最终块 → H'(dkLen) Uint8Array(raw hash) base64Encode(raw) success(Base64) String replaceAll('\n', '') Future<String>

这条时序里有三个必须与 Android / iOS 完全一致的点:

  • password 与 salt 的字节化:三端都按 UTF-8 编码。Android 用 String.toByteArray()(默认 UTF-8),iOS 用 CatCrypto 的 UTF-8 转换,ArkTS 侧用 Bytes.ets 里的 utf8Encode 手写实现(不用 TextEncoder,避免不同引擎对孤立代理项的处理差异);
  • H0 里写进参数块的数字:p、dkLen、m、t、version、type 全部按小端 32 位写入,顺序错一个字段,结果就完全不同;
  • 最终输出的编码:只做 Base64,不加 PHC 前缀、不做十六进制。

4.2 参数校验分两层

Dart 层只校验一件事:salt.length < 8 时直接抛 Fargon2ArgumentError,请求根本不会发出。其余参数(hashLength、iterations、parallelism、memoryKibibytes)在 Dart 层不做校验,交给原生侧。

ArkTS 侧做了防御性校验,覆盖 Dart 层没管的部分:

校验项规则错误码
四个数值参数必须为整数(Number.isInteger)FARGON2_BAD_ARGS
salt长度 ≥ 8FARGON2_BAD_ARGS
hash_length≥ 4FARGON2_BAD_ARGS
iterations≥ 1FARGON2_BAD_ARGS
parallelism≥ 1FARGON2_BAD_ARGS
memory_kibibytes≥ 8 × parallelismFARGON2_BAD_ARGS
memory_kibibytes≤ 1 GiB(MAX_MEMORY_KIB)FARGON2_BAD_ARGS
计算过程异常try/catch 捕获FARGON2_ERROR

memory_kibibytes 的上限是本适配新增的保护:纯 ArkTS 实现在 64 MiB 这一档已经会出问题(见 8.6 与 9.7),没有上限时一个错误参数就可能把进程拖死。上限取 1 GiB 是为了与参考实现的推荐区间对齐,同时给出明确的错误而不是无响应。

4.3 公开 API

名称类型入参返回OHOS 支持
Fargon2构造函数Fargon2Mode modeFargon2yes
Fargon2.hash方法passphrase、salt、hashLength=32、iterations=3、parallelism=2、memoryKibibytes=65536Future<String>(Base64)yes
Fargon2Mode枚举—argon2i / argon2d / argon2idyes
Fargon2ArgumentError异常message—yes(Dart 层抛出)

通道契约:

项值
通道名fargon2
方法名hash
入参键mode、password、salt、hash_length、iterations、parallelism、memory_kibibytes
返回Base64(raw hash 字节)
错误码FARGON2_BAD_ARGS、FARGON2_ERROR

注意入参键名是 password 而不是 passphrase:Dart 层的参数叫 passphrase,但发给原生侧的键是 password,这是上游既有约定,适配时按原样保留。

4.4 为什么 Dart 层可以零改动

因为 fargon2.dart 里没有任何平台分支:

  • 没有 Platform.isAndroid / Platform.isIOS 之类的判断;
  • 没有 defaultTargetPlatform 或 kIsWeb 分支;
  • 没有能力探测(不询问平台「你支持 Argon2 吗」)。

所以只要原生侧实现了同名通道、同名方法、同键名入参、同类型返回值,Dart 层不需要加 ohos 分支就能直接工作。这与需要判断平台的库(例如 app_local 那类带 Platform.isXxx 的)形成对比:那类库必须补一个 isOhos 分支,否则会静默走错路径。


五、ArkTS 平台实现

5.1 插件注册

ohos/index.ets 只有两行有效代码:

import Fargon2Plugin from './src/main/ets/components/plugin/Fargon2Plugin';
export default Fargon2Plugin;

插件类实现 FlutterPlugin 与 MethodCallHandler:

export default class Fargon2Plugin implements FlutterPlugin, MethodCallHandler {
  private channel: MethodChannel | null = null;
  private argon2: Argon2 = new Argon2();

  getUniqueClassName(): string {
    return 'Fargon2Plugin';
  }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME);
    this.channel.setMethodCallHandler(this);
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    if (this.channel != null) {
      this.channel.setMethodCallHandler(null);
      this.channel = null;
    }
  }
}

CHANNEL_NAME 常量写死为 'fargon2',并带注释指回 Dart 侧的 const MethodChannel('fargon2')——这类「两处字符串必须一致却不能靠编译器约束」的约定,写注释是最省事的维护方式。

onDetachedFromEngine 里先把 handler 置空再置空 channel,避免引擎销毁后插件实例仍被通道持有:Flutter OH 的插件对象生命周期与引擎绑定,漏掉这一步在反复热重启时容易积累引用。

example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets 由工具链生成,内容是:

const TAG = "GeneratedPluginRegistrant";

export class GeneratedPluginRegistrant {
  static registerWith(flutterEngine: FlutterEngine) {
    try {
      flutterEngine.getPlugins()?.add(new Fargon2Plugin());
    } catch (e) {
      Log.e(TAG, "Tried to register plugins with FlutterEngine (...) failed.");
      Log.e(TAG, "Received exception while registering", e);
    }
  }
}

它引用的是 from 'fargon2',也就是 ohos/oh-package.json5 中声明的 HAR 名称。改 HAR 名称(例如加 fluttertpc_ 前缀)时必须同步 pubspec.yaml 的 pluginClass 与这里的导入名,否则注册阶段就会失败。

5.2 方法分发与参数读取

onMethodCall(call: MethodCall, result: MethodResult): void {
  try {
    if (call.method === METHOD_HASH) {
      this.handleHash(call, result);
    } else {
      result.notImplemented();
    }
  } catch (err) {
    result.error(ERR_CODE, 'Failed to handle ' + call.method + ': ' + error.message, '');
  }
}

未实现的方法返回 notImplemented(),对应 Dart 侧的 MissingPluginException——这是 Flutter 的跨平台约定,不要改成 result.success(null),否则业务侧会把「方法不存在」当成「返回空值」。

handleHash 先一次性把所有参数取出来,再逐项校验:

mode = call.argument('mode') as string;
password = call.argument('password') as string;
salt = call.argument('salt') as string;
hashLength = call.argument('hash_length') as number;
iterations = call.argument('iterations') as number;
parallelism = call.argument('parallelism') as number;
memoryKibibytes = call.argument('memory_kibibytes') as number;

memory_kibibytes 在 Dart 侧默认是 65536,如果业务没传、原生侧又按 0 处理,就会命中「小于 8 × parallelism」的校验。所以这里对每个键都做了显式取值,不做「缺省补默认值」的猜测——补默认值会让「业务传了非法值」和「业务没传值」两种情况的错误信息混在一起。

5.3 为什么是自实现,而不是降级或返回不支持

这是本次适配最关键的决策,三个备选方案都被否掉了:

  1. 返回 null 或抛「不支持」:fargon2 的语义就是「给我一个哈希」,返回空值会让业务侧的 hash() 调用直接崩在后续解码上,或者更糟——静默存了一个空口令哈希。这个库在 OHOS 上就彻底不可用了。
  2. 降级成 PBKDF2 / SCRYPT:系统给了 PBKDF2 与 SCRYPT,但它们是不同的算法,产出的哈希与 Android / iOS 不一致。同一份口令在鸿蒙上算出 A、在 Android 上算出 B,服务端校验就会一半失败一半通过。
  3. 在 ArkTS 侧实现 Argon2:唯一能同时满足「跨端一致」和「功能可用」的方案,代价是性能(见 5.5 与 8.6)。

判断依据是先确认系统确实没有:在 SDK 的 .d.ts 里全量检索 argon 没有任何匹配,cryptoFramework 的 Md / Mac / Kdf 能力列表里也没有 Argon2 相关接口。这个结论写进了 CHANGELOG.OpenHarmony.md 与两份 README,避免后来者重复踩一遍。

5.4 算法实现结构

四个文件的分工是一条直线:Bytes 提供字节工具,Blake2b 提供哈希原语,Argon2 组合出算法,Fargon2Plugin 负责对外。

Bytes.ets(95 行):UTF-8 编码、Base64 编码、小端读写。UTF-8 是手写的,逐码点处理 1~4 字节序列,目的是与 Android 的 toByteArray()、iOS 的 UTF-8 转换在「中文」「emoji」「孤立代理项」这些边界上保持一致。

Blake2b.ets(218 行):Argon2 的 H0(参数块哈希)与 H’(变长输出)都建立在 BLAKE2b 上。实现里保留了 64 位运算的 (low, high) 双 32 位表示,并对 IV 常量、sigma 置换表、G 函数的轮转位数逐项对齐参考实现。

Argon2.ets(486 行):三种变体共用一套填充逻辑,差异集中在索引计算:

阶段处理方式
H0BLAKE2b-512(p, dkLen, m, t, version, type, 口令长度, 口令, salt 长度, salt, 秘密, 附加数据)
初始块由 H0 派生两个 1 KiB 块,分别作为 B[0]、B[1]
填充按 p 分车道,逐块计算参考块索引——argon2d 用前一块内容决定,argon2i 用计数器伪随机,argon2id 前两轮用 i、之后用 d
收尾各车道末块异或 → 最终块 → H’(dkLen)

64 位算术全部用 (low, high) 两个 number 表示,而不是 BigInt:填充阶段是热点路径,BigInt 在这类循环里会产生大量装箱与 GC 压力。代价是每个加、乘、移位都要手写进位,这与参考实现的运算顺序必须逐位一致——这也是本次适配花时间最多的部分。

Fargon2Plugin.ets(209 行):通道、校验、Base64 转换、hilog。成功日志里带上了完整参数与耗时,排查性能问题很方便:

hilog.info(DOMAIN, TAG,
  'hash ok, mode: %{public}s, dkLen: %{public}d, t: %{public}d, m: %{public}d KiB, p: %{public}d, cost: %{public}d ms',
  mode, hashLength, iterations, memoryKibibytes, parallelism, Date.now() - start);

hilog 的参数必须用 %{public}s / %{public}d 明确标注可见性,否则默认按隐私数据处理,在日志里会显示成 <private>,等于没打。

5.5 正确性如何保证

纯自实现的算法最怕「看起来能跑,结果全错」。本适配用了两道校验:

第一道:宿主机与参考实现对拍。 把 Argon2.ets、Blake2b.ets、Bytes.ets 原样复制成 .ts(这三个文件不依赖任何 @ohos.* 接口,因此可以直接用 tsc 编译成 JS 在 Node 下跑),再与 argon2-cffi(PHC 参考 C 实现的 Python 绑定,ARGON2_VERSION=19)逐字节比较。覆盖 23 组参数:三种模式、t=1..4、p=1..4、m=8..65536 KiB、dkLen=4..129(含非 4 的倍数)、中文与 emoji 口令。结果全部一致,报告在 docs/ohos/argon2-vectors.md。

第二道:本次真机输出与本机参考实现复核。 验证时在设备上跑的每一组参数,都在宿主机用 argon2-cffi 重算一遍,逐字节对照:

参数(口令 mypassphrase,盐 mysalt01)真机 Base64本机 argon2-cffi结论
argon2id / t=3 / p=2 / m=4096 KiB / dk=32GIjNhcX2T7sEYljQaCj+vKOPLb8TkQUEJ+Rkzf2+JLM=GIjNhcX2T7sEYljQaCj+vKOPLb8TkQUEJ+Rkzf2+JLM=一致
argon2id / t=1 / p=1 / m=64 KiB / dk=32wAIkIqKRcMNrX/PIOEvGvMYD0Z8GtRXskjQSZ19o3Y=wAIkIqKRcMNrX/PIOEvGvMYD0Z8GtRXskjQSZ19o3Y=一致
argon2i / t=1 / p=1 / m=64 KiB / dk=32Xsi5v8gpiW28Db/KtfyRQ/h8QXV5Z54YTjDNeO3473E=Xsi5v8gpiW28Db/KtfyRQ/h8QXV5Z54YTjDNeO3473E=一致

第三行的 hex 也逐位相同(5ec8b9bfc829896dbc0dbfcab5fc9143f87c417579679e184e30cd7b4df8ef71),说明不只是 Base64 编码对得上,中间结果也一致。

argon2id 与 argon2i 在同一组参数下产出的值不同(wAIkIqKR… vs Xsi5v8gp…),这从反面证明模式参数确实传到了算法层,而不是被忽略后统一按一种变体算。

5.6 权限

无。

  • 插件侧:ohos/src/main/module.json5 只有模块声明(name、type: har、deviceTypes),没有任何 requestPermissions;
  • 宿主侧:example/ohos/entry/src/main/module.json5 的 requestPermissions 是空数组 []。上游模板原本带了一条 ohos.permission.INTERNET,本次适配删掉了——Argon2 计算不联网,留着只会让用户在权限列表里看到一个与功能无关的条目。

六、补全交付文件并提交适配分支

6.1 除代码外还要补全哪些文件

文件作用
README.OpenHarmony_CN.md中文说明:安装方式、TAG 对应表、兼容性、权限、性能、API、实现差异
README.OpenHarmony.md英文版,内容与中文版对齐
CHANGELOG.OpenHarmony.md适配版本变更记录,包含已知限制(性能)
docs/ohos/argon2-vectors.md23 组参考向量对拍报告与复现方法
docs/ohos/*.jpeg、hilog-ohos.log真机证据
example/ohos/README.md宿主工程的构建、签名、运行说明
example/lib/main.dart从模板页面重写为可验证的演示页

写入文档时有三处需要核对,本文如实记录当前状态:

  • 下载地址:两份 README 的 pubspec.yaml 示例里,仓库地址写的是 https://atomgit.com/CPF-Flutter/fluttertpc_fargon2,而实际配套仓库是 https://atomgit.com/oh-flutter/fargon2。组织名与仓库名都不一致,读者按文档复制会拉不到代码,发布前必须统一;
  • TAG 表的分支列:README 写的是 br_v1.0.1_ohos,实际分支是 main。这列目前只是「建议分支」,不影响按 TAG 拉取,但会让人误以为存在这个分支;
  • 许可证:上游是 MIT,ohos/oh-package.json5 里的 license 也写的是 MIT,这一处是一致的,无需改动。

6.2 提交前检查

example/ohos/build-profile.json5 里当前带着本机签名材料:

"signingConfigs": [
  {
    "name": "default",
    "type": "HarmonyOS",
    "material": {
      "certpath": "C:\\Users\\PC\\.ohos\\config\\default_ohos_....cer",
      "keyAlias": "debugKey",
      // ...

这段内容已经随适配提交 6be4e42 进入仓库(可以用 git show 6be4e42:example/ohos/build-profile.json5 查到)。证书路径里带着本机用户名,口令字段也在其中,属于个人环境信息,提交前应当还原为:

"signingConfigs": []

然后用自己的调试签名在本地重新生成(做法见 8.3)。如果仓库已经推送,还要按接收仓库的安全要求决定是否需要清理历史——仅仅在最新提交里删掉,旧提交里依然能查到。

另一个提交前要确认的点是 example/ios/Flutter/ephemeral/:这次 git status 里它显示为未跟踪目录,属于 iOS 构建产物,应确认 .gitignore 覆盖了它,不要误提交。

6.3 提交并推送到 AtomGit

当前仓库的 origin 指向上游 GitHub,无法直接推送适配提交,需要先把远端换成自己的 AtomGit 仓库:

git remote rename origin upstream
git remote add origin https://atomgit.com/oh-flutter/fargon2.git
git remote -v

提交与打 TAG:

git add -A
git commit -m "feat(ohos): implement Argon2 in ArkTS and add OpenHarmony support"
git tag -a 1.0.1-ohos-1.0.0-beta.1 -m "fargon2 1.0.1 OpenHarmony adaptation beta.1"
git push -u origin main
git push origin 1.0.1-ohos-1.0.0-beta.1

保留 upstream 指向 GitHub 的好处是后续可以 git fetch upstream 跟踪上游更新,再 rebase 到自己的 main 上。提交信息建议写清三件事:为什么自实现(系统无 Argon2)、验证到什么程度(参考向量 + 真机)、已知限制(性能),这样后续维护者不必翻代码就能判断改动范围。


七、使用根目录 example 演示接入

7.1 本地适配时使用路径依赖

example/pubspec.yaml 默认就是指向上级的路径依赖,改完库代码直接生效:

dependencies:
  flutter:
    sdk: flutter
  fargon2:
    path: ../

在 example/ 目录执行 flutter pub get,再 flutter run -d <device-id> 即可。路径依赖的好处是改 ArkTS 代码后不需要发版;注意改完 ohos/ 下的 .ets 之后要重新构建 HAP,热重载不会重新编译原生侧。

7.2 通过 AtomGit 引入库

业务工程引用适配版时,用 git 依赖加 TAG:

dependencies:
  fargon2:
    git:
      url: https://atomgit.com/oh-flutter/fargon2.git
      ref: 1.0.1-ohos-1.0.0-beta.1

执行 flutter pub get 后,Flutter 工具链会读取插件的 pubspec.yaml,识别出 ohos 平台声明,并在宿主工程的 GeneratedPluginRegistrant.ets 里生成注册代码。宿主工程本身仍需要是 OHOS 模板工程(ohos/ 目录、build-profile.json5、entry 模块齐全),插件不会替你生成这些。

7.3 计算口令哈希

import 'package:fargon2/fargon2.dart';

final String hash = await const Fargon2(mode: Fargon2Mode.argon2id).hash(
  passphrase: 'mypassphrase',
  salt: 'mysalt01',      // 至少 8 个字符
  hashLength: 32,
  iterations: 3,
  parallelism: 2,
  memoryKibibytes: 4096, // 单位 KiB,鸿蒙上建议 4 MiB~16 MiB
);
// hash 是 raw hash 的 Base64;入库时连同 salt 一起存

三个模式换 mode 即可,参数语义与 Android / iOS 完全一致。

7.4 业务侧的三条注意事项

  1. salt 必须每个用户独立生成并随哈希一起存。本库不生成 salt,也不做 PHC 串拼接,salt 由业务提供;用固定 salt 等于让相同口令得到相同哈希,彩虹表照旧有效。
  2. 鸿蒙上的参数要收敛。memoryKibibytes 默认 65536(64 MiB)是给 C 实现准备的,纯 ArkTS 在这个档位不可用(见 8.6)。建议在 OHOS 上显式传 4096~16384;如果业务需要「全平台参数一致」,那就得接受鸿蒙侧偏慢,或者等后续 C++(NAPI)实现。
  3. 必须 await,不要在计算期间做重 UI 操作。计算发生在平台主线程上,与 Android / iOS 的实现方式一致;计算期间页面无法响应交互,参数越大越明显。

八、验证、构建与鸿蒙设备运行效果

8.1 分别验证库与 example

本仓库包含单元测试 test/fargon2_test.dart,用 setMockMethodCallHandler 把通道打桩成返回固定 Base64,覆盖两个用例:

  1. hash() 正常路径返回打桩值,验证 Dart 层的参数组装与返回值透传;
  2. salt 只有 4 个字符时抛 Fargon2ArgumentError,且 toString() 与预期文案完全一致。

从仓库根目录执行:

flutter pub get
flutter test
flutter analyze
cd example
flutter pub get
flutter analyze

单元测试验证的是 Dart 层,与平台无关;Argon2 的实际计算、参数校验与 Base64 编码只能在鸿蒙设备上确认。example/ 的 analysis_options.yaml 里补了 analyzer.exclude(build/**、android/**、ios/**),否则 flutter analyze 会扫到生成的原生工程。

8.2 确认设备连接

hdc list targets
flutter devices

设备首次连接电脑时,需要在手机端确认调试授权。本次验证使用的设备信息:

hdc shell param get const.ohos.fullname      # OpenHarmony-6.1.1.120
hdc shell param get const.product.model      # TLR-AL00
hdc shell param get const.ohos.apiversion    # 24
hdc shell param get const.product.cpu.abilist # arm64-v8a

8.3 配置签名

真机安装的 HAP 需要有效签名。本仓库当前提交里的 signingConfigs 是别人的本机材料,不能直接用(见 6.2),请替换为自己的调试签名:

  1. 用 DevEco Studio 打开 example/ohos,不是仓库根目录;
  2. 等待工程 Sync 成功,确认 Project 视图中存在 entry 模块;
  3. 打开 File > Project Structure > Signing Configs;
  4. 勾选 Automatically generate signature;
  5. 确认设备、应用包名、证书和 Profile 匹配(调试 Profile 与 bundleName: dev.iktakahiro.fargon2_example 强绑定);
  6. 再回到终端执行构建或安装。

8.4 构建与安装

在 example/ 目录执行 flutter run -d <device-id> 可以直接构建运行。仅构建 HAP 时:

flutter build hap --debug

Windows 环境下该命令可能因 DevEco Studio 26 的 ohpm.bat 批处理递归缺陷中断(见 9.3),可改用工程内等价的构建命令:

cd example/ohos
export JAVA_HOME="/d/Program Files/Huawei/DevEco Studio/jbr"
export PATH="/d/Program Files/Huawei/DevEco Studio/jbr/bin:$PATH"
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon

本次验证用的正是后者。第一次执行时在打包阶段失败:

> hvigor ERROR: Failed :entry:default@PackageHap...
> hvigor ERROR: Error Code: 00308018 Unknown Error
spawn java ENOENT

把 DevEco Studio 自带的 JBR 加入 PATH 后重新执行,构建通过:

> hvigor Finished :entry:default@SignHap... after 3 s 949 ms
> hvigor Finished :entry:assembleHap... after 1 ms
> hvigor BUILD SUCCESSFUL in 21 s 774 ms
42 tasks in total: 20 executed, 22 up-to-date

产物位于 example/ohos/entry/build/default/outputs/default/entry-default-signed.hap(约 138 MiB,debug 包带调试符号与完整 flutter_assets)。真机安装:

hdc install -r entry-default-signed.hap
hdc shell aa start -a EntryAbility -b dev.iktakahiro.fargon2_example

pack.info 中的 apiVersion 为 {"compatible": 24, "releaseType": "Release", "target": 24},与测试设备(API 24)一致。

需要说明的是本次重新构建的原因:仓库里原有的 HAP 是 16:29 打的,而 Argon2.ets 的最后修改时间是 16:32——产物比源码旧,不能作为当前代码的验证证据。重新构建后 :entry:default@CompileArkTS 重新执行,产物时间为 18:31,与提交内容对应。

8.5 在设备上测试哈希语义

example 页面按「输入 / 参数 / 结果 / 运行日志」分区,逐项核对:

  1. 启动应用:默认参数(argon2id / dk=32 / t=3 / p=2 / m=4096 KiB)自动计算一次,首屏头部显示「调用 1 次」,结果区出现 Base64、HEX、耗时与字节数(图 5、图 6 左);
  2. 核对默认参数的输出:GIjNhcX2T7sEYljQaCj+vKOPLb8TkQUEJ+Rkzf2+JLM=,耗时 6712 ms——同一组参数在宿主机上算出来的值与它逐字节相同(见 5.5);
  3. 点按 快速预设(t=1 / p=1 / m=64 KiB)再点 计算哈希:结果变为 wAIkIqKR…,耗时降到 131 ms,日志区出现「已载入快速预设」(图 7 左);
  4. 再点一次 计算哈希:结果不变,结果区出现绿色「重复计算一致」标记,日志累计到 8 条(图 6 右);
  5. 切换模式为 argon2i 后重算:结果变为 Xsi5v8gp…,耗时 125 ms,结果区标签显示「模式 argon2i」(图 7 右);
  6. 点按 载入参考向量(argon2id / t=3 / p=2 / m=64 MiB / dk=16)再计算:页面在十余秒后退出到桌面,应用被系统终止(图 8 左,结论见 8.6 与 9.7);
  7. 重新启动应用:默认参数自动计算恢复正常,头部重新显示「调用 1 次」(图 8 右)。

前 5 项与第 7 项已在真机完成,第 6 项按「应用被终止」这一事实记录,没有把它写成「通过」。

8.6 鸿蒙设备运行效果

先看输入与参数区。页面的参数控件与 Dart 侧 API 一一对应,内存下拉框下方直接标出 m ≥ 8 × p 的约束:

启动后的首屏参数区
平台 ohos、通道 fargon2、「调用 1 次」、默认模式 argon2id哈希长度 32 字节、迭代 3、并行度 2、内存 4096 KiB(4 MiB),提示「最小值 8 × p = 16 KiB」

图 5:示例页的输入与参数区,以及启动后自动完成的第一次计算。

默认参数(argon2id / 4 MiB / t=3 / p=2 / dk=32)的计算结果与完整日志:

默认参数计算结果重复计算与日志
Base64 GIjNhcX2T7sEYljQaCj+vKOPLb8TkQUEJ+Rkzf2+JLM=,HEX 1888cd85…be24b3,32 B,耗时 6712 ms,模式 argon2id同参数再算一次:113 ms,结果不变,出现「重复计算一致」;日志 8 条覆盖 4096 KiB→64 KiB→重复计算

图 6:默认参数(argon2id / 4 MiB / t=3 / p=2 / dk=32)的计算结果,以及同参数重复计算的一致性结论。

换到快速预设与 argon2i,可以看到内存代价对耗时的影响,以及模式参数确实生效:

快速预设(argon2id / 64 KiB)切换到 argon2i
t=1 / p=1 / m=64 KiB,耗时 131 ms,Base64 wAIkIqKR…同样 64 KiB,耗时 125 ms,Base64 Xsi5v8gp…,与 argon2id 结果不同

图 7:快速预设(64 KiB)与 argon2i 的结果。两者参数相同、模式不同,输出不同,说明 mode 确实传到了算法层。

64 MiB 参考向量这一档的结果,以及重启后的恢复状态:

KeyHash 示例页 KeyHash 示例页 KeyHash 示例页 KeyHash 示例页 KeyHash 示例页 KeyHash 示例页 KeyHash 示例页 KeyHash 示例页

64 MiB(载入参考向量后计算)重新启动应用
点击计算后约十余秒,应用退出到桌面,进程被系统终止aa start 再次启动,默认参数自动计算恢复,头部重新显示「调用 1 次」

图 8:64 MiB 参数下应用被系统终止,重新启动后功能恢复正常。

关于第 6 步的终止:设备上 /data/log/faultlog/faultlogger/ 目录对普通 shell 返回 Permission denied,本次没有取到确切的终止原因,因此文中只陈述现象。从代码路径看,两个可疑点都指向同一处设计:Argon2 在平台主线程上同步计算 64 MiB 数据,其间无法响应任何事件;同时 ArkTS 侧对 64 MiB 的 Uint8Array 与中间块会产生多次分配与拷贝,内存峰值远高于 64 MiB 本身。这两者中的任意一个都足以让应用被系统判定为异常。要得到确定结论,需要在 faultlogger 可读的环境(如开放 root 的开发机)复现并读取 appfreeze / cppcrash 记录。

插件侧的日志与界面数据一一对应,TAG 为 Fargon2Plugin(下面按 TAG 裁剪了模块前缀列):

16:29:53.516  FlutterEngineCxnRegistry --> Adding plugin: Fargon2Plugin
16:29:53.516  attached to engine, channel [fargon2] registered
16:29:55.196  onMethodCall: hash
16:30:01.628  hash ok, mode: argon2id, dkLen: 32, t: 3, m: 4096 KiB, p: 2, cost: 6432 ms

第一段是 16:29 那次运行的日志,cost: 6432 ms 与当次界面显示的 6.4 s 对得上;本次 18:32 复跑时界面显示 6712 ms,属于同一档的正常波动(实测区间 6.4~6.7 s)。日志里的 m: 4096 KiB 也直接证明 Dart 侧传下来的 memory_kibibytes 没有被截断或改写。

性能边界可以按内存代价线性外推,实测与推算的对照如下:

参数(argon2id,t=3,p=2)真机耗时说明
m=64 KiB131 ms快速预设(t=1、p=1)
m=4 MiB6.4~6.7 s默认参数,可用但偏慢
m=16 MiB约 25 s(按 4 MiB 线性推算,未实测)已接近可接受上限
m=64 MiB未完成计算过程中应用被终止(见上)

这组数据也解释了为什么 README 把「4 MiB~16 MiB」写成建议区间,而不是照搬 Dart 默认的 64 MiB。


九、FAQ:适配过程与使用问题

9.1 Missing SDK components

典型错误如下:

Missing SDK components. SDK path: ...,
missing components: toolchains,ets,js,native,previewer.

这个错误发生在 Hvigor 同步阶段。通常需要检查构建工具使用的 SDK 路径、组件是否完整,以及 Hvigor 与 SDK 的版本是否匹配。

处理顺序:

  1. 在 DevEco Studio SDK Manager 中确认对应 API 的组件已经下载完整;
  2. 检查 Flutter 和 DevEco Studio 使用的 SDK 路径是否一致;
  3. 避免误用不完整的 SDK 目录;
  4. 确认 SDK 根目录下存在 toolchains、ets、js、native、previewer;
  5. 执行 flutter config --ohos-sdk <正确路径>;
  6. 重新执行 flutter doctor -v 和 DevEco Studio Sync。
为什么连接 API 24 手机仍然会报这个错误?

因为 Sync 和 Compile 首先读取宿主机本地 SDK。手机 API 版本只在部署、安装和运行时参与兼容判断。即使完全不连接手机,本地 SDK 不完整时也会得到相同错误。

当前工程的 compatibleSdkVersion 是 6.1.1(24)(API 24),因此 API 24 设备在安装版本门槛上是满足的。

9.2 spawn java ENOENT

本次实际遇到的错误,出现在 PackageHap 阶段:

> hvigor ERROR: Failed :entry:default@PackageHap...
> hvigor ERROR: Error Code: 00308018 Unknown Error
spawn java ENOENT

hvigorw 是 Node 脚本,打包与签名阶段会再起 java 进程。只设置 JAVA_HOME 不够——它需要能在 PATH 里直接找到 java 可执行文件。DevEco Studio 自带 JBR,把它加进 PATH 即可:

export JAVA_HOME="/d/Program Files/Huawei/DevEco Studio/jbr"
export PATH="/d/Program Files/Huawei/DevEco Studio/jbr/bin:$PATH"

Windows 下路径按 Git Bash 风格写 /d/...;在 CMD / PowerShell 里对应 D:\...\jbr\bin。改完重新执行同一条 hvigorw assembleHap,本次由 45 s 失败变为 21 s 通过。

为什么前面的编译阶段是成功的,只在打包时失败?

因为 CompileArkTS、ProcessCompiledResources 这些任务由 hvigor 自己的构建进程完成,直到 PackageHap 与 SignHap 才需要调用外部 java(打包工具与签名工具都是 Java 程序)。所以表现是「代码编译没问题,最后一步突然 ENOENT」。

9.3 flutter build hap 卡在 ohpm install:BATCH RECURSION exceeds STACK limits

Windows + DevEco Studio 26 上执行 flutter build hap 时,可能在依赖安装阶段中断:

[ohpm] BATCH RECURSION exceeds STACK limits

原因是 Flutter 工具链调用 ohpm.bat 时触发了批处理递归。绕过方式是直接用工程内的 hvigorw 构建(8.4 的命令),它与 flutter build hap 的最终产物一致,只是少了 Flutter 侧的前置步骤。

直接 hvigorw 构建会不会漏掉 Dart 代码的编译?

不会。flutter build hap 内部做的事情是「先编译 Dart(生成 kernel_blob.bin 与 flutter_assets),再调 hvigor 打包」。如果 example/ohos 里已经有上次构建的 flutter_assets,而 Dart 代码没有变化,直接 hvigorw assembleHap 得到的产物就是完整的。

但改了 Dart 代码后必须先 flutter build hap(或 flutter run)重新生成 flutter_assets,否则 HAP 里跑的还是旧 Dart 代码。本次验证前源文件的修改时间都早于上一次 Flutter 构建产物,且只重新构建了 ArkTS 侧,因此 Dart 侧无需重编。

9.4 Invalid kernel binary format version (expected 130, found 138)

--release 构建时可能遇到:

Invalid kernel binary format version (expected 130, found 138)

这是 Flutter 引擎产物(gen_snapshot)与当前 Dart SDK 版本不匹配,发生在 AOT 阶段,与插件代码无关。处理方式:

  1. 改用 --debug 构建(本次验证全程使用 debug);
  2. 或执行 flutter precache --ohos(或对应平台的 precache)重新拉取引擎产物;
  3. 确认 flutter --version 与工程使用的 Flutter SDK 是同一份。

9.5 安装报 code:9568297 older sdk version in the device

install failed due to grant request permissions failed
error: failed to install bundle. code:9568297

这条错误的名字容易误导,实际原因通常是设备的 API 版本低于 HAP 声明的 compatibleSdkVersion。本工程声明 6.1.1(24),API 24 及以上设备可安装;低于 24 的设备需要下调 compatibleSdkVersion(不推荐,会引入未验证的行为差异)。

9.6 安装报 no signature file 或 install version downgrade

  • no signature file:装的是 entry-default-unsigned.hap。选 entry-default-signed.hap;
  • install version downgrade:设备上已装的包版本更高。加 -r 覆盖安装,或先 hdc uninstall <bundleName>。

9.7 计算 64 MiB 参数后应用直接退出

本次实测现象:argon2id / t=3 / p=2 / m=64 MiB / dk=16 点击计算后约十余秒,应用回到桌面,进程被系统终止。这是当前纯 ArkTS 实现的能力边界,不是参数校验或通道的问题。

规避方式:

  1. 把 memoryKibibytes 控制在 4096~16384;
  2. 确需大内存代价时,在插件内改用 C++(NAPI)实现参考 C 版本 Argon2——本适配没有包含这一部分;
  3. 若要「快速失败」而不是「卡住被杀」,可以在业务侧先把参数限制在安全区间,再调用 hash()。
为什么 4 MiB 只要 6.7 秒,64 MiB 不是 100 秒左右吗?

按填充阶段的内存块数量线性外推,64 MiB 大约是 4 MiB 的 16 倍,理论耗时在 100 s 量级。但真机上应用在十余秒就被终止,说明先触发的不是「算完」,而是「被打断」——所以 64 MiB 在鸿蒙上的真实结论是「不可用」,而不是「慢一点」。

9.8 默认参数 6.7 秒,正常吗

正常。4 MiB / t=3 / p=2 在 nova 14 上实测 6.4~6.7 s。纯 ArkTS 与 C 实现差了两个数量级:同样的参数在 Android 的 argon2kt 上是几十毫秒量级。这是「用可维护的 ArkTS 换掉 JNI + C 依赖」的代价,属于已知限制,写进了 CHANGELOG.OpenHarmony.md 与两份 README。

如果业务对首次哈希的等待时间敏感,可以在 OHOS 上使用较低的内存代价(4 MiB)并配合一次性的「等待提示」,而不是把参数降到不安全的水平。

9.9 salt 少于 8 个字符时抛出 Fargon2ArgumentError

Fargon2ArgumentError: salt is too short. It must be at least 8 characters.

这个异常来自 Dart 层(lib/fargon2.dart),不会走到通道,所以三端行为一致、也不会有 PlatformException。注意它检查的是字符数而不是字节数:中文 salt 的 UTF-8 字节数更长,但字符数不足 8 时同样会被拒绝。

9.10 抛 MissingPluginException 或 hash 一直不返回

MissingPluginException 表示通道没有注册上,排查顺序:

  1. pubspec.yaml 里是否有 ohos: pluginClass: Fargon2Plugin;
  2. ohos/index.ets 是否导出 Fargon2Plugin;
  3. 宿主工程的 GeneratedPluginRegistrant.ets 是否包含 add(new Fargon2Plugin());
  4. 改完 ArkTS 代码后是否重新构建了 HAP(热重载不会重编原生侧)。

hash 一直不返回则通常是参数过大(见 9.7)——同步计算期间不吐任何中间状态,只能等它结束。

9.11 flutter create 不认识 ohos

Could not find an option named "platforms".

或提示 ohos 不是合法平台,说明当前 Flutter 不是 Flutter OH 版本。执行 flutter --version 确认是 3.47.7-ohos-*,并检查 flutter config --list 里的 SDK 路径指向支持 OHOS 的工具链。

9.12 AtomGit 依赖提示找不到分支或无权限

git 依赖常见的三种报错:

  1. Could not find a file named "pubspec.yaml" —— url 写错或 ref 指向不存在的 TAG;
  2. fatal: could not read Username —— 仓库是私有的,需要配置凭据或改用带 token 的地址;
  3. 解析到旧代码 —— ref 写的是分支名,分支已前进。发布给业务用时应固定 TAG。

9.13 改了本地库代码,Demo 为什么没变化

两条缓存链路要分别处理:

  • Dart 侧:改 lib/*.dart 后需要 flutter run 重新编译(热重载不重编 Dart 的常量与结构变更);
  • ArkTS 侧:改 ohos/**/*.ets 后必须重新构建 HAP 并重装,热重载完全不涉及原生代码。

判断有没有生效,看两处:结果是否符合预期,以及 hilog 里 Fargon2Plugin 的 hash ok 日志是否重新出现。


相关链接

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐