HarmonyOS鸿蒙App 增加文件级 AES 加密存储能力,用两个方法实现敏感文本的加密落盘与解密还原,纯 Dart 实现零权限开箱即用 —— file_encryptor 鸿蒙使用实战指南
开发工具: 华为云码道
本文配套仓库: 上游 m-abdulmonem/file-encryptor;鸿蒙适配改动位于本地仓库的
ohos/、example/ohos/、README.OpenHarmony_CN.md、README.OpenHarmony.md、CHANGELOG.OpenHarmony.md与docs/ohos-test/。
鸿蒙适配后仓库:https://atomgit.com/oh-flutter/file-encryptor
本文配套仓库:https://github.com/m-abdulmonem/file-encryptor(TAG:0.0.1-ohos-1.0.0-beta.1,分支:main),文中示例代码位于仓库 example/ 目录。

文件加密是移动应用中保护本地敏感数据的基础手段。 当应用需要在设备本地存储用户隐私信息、API 密钥、会话令牌等敏感数据时,明文落盘存在被逆向或窃取的风险。
file_encryptor库提供了一个简洁的方案:用 AES-128-CBC 算法将文本内容加密后以 base64 编码写入.aes文件,解密时读取该文件还原原文。加解密逻辑全部是纯 Dart 实现,通过encrypt包完成 AES 运算,不依赖任何原生平台 API——鸿蒙应用同样开箱即用,Dart 层零改动。
本文介绍如何使用鸿蒙化适配后的 Flutter 三方库 file_encryptor,在鸿蒙 App 内通过两个方法完成文本的 AES 加密落盘与解密还原,并附上 OpenHarmony 6.1.1.120 真机的完整实测记录。
一、最终运行效果
应用启动后展示交互式加解密演示页:在输入框中输入任意文本,点击"Encrypt"按钮将文本 AES 加密后以 base64 编码写入 test_file.aes 文件,界面展示密文内容和文件大小;点击"Decrypt"按钮读取该文件并解密还原原文:
| 验证点 | 结果 |
|---|---|
| 应用启动,Flutter 页面正常渲染 | 通过 |
输入 Hello OpenHarmony!,点击 Encrypt,生成 test_file.aes(44 bytes) | 通过 |
密文以 base64 字符串形式展示(neLv/0I4Wn2M/BX6nm4IZ7Ch73Xf+uo2g3YXvd5xhIw=) | 通过 |
点击 Decrypt,还原原文 Hello OpenHarmony!,加解密往返一致 | 通过 |
密文文件路径为 /data/storage/el2/base/files/flutter/test_file.aes | 通过 |
| 全程无需申请任何权限 | 通过 |
| Dart 层零改动 | 通过 |
| 操作 | 预期表现 |
|---|---|
| 输入文本并点击 Encrypt | 生成 ${path}.aes 文件,状态显示文件大小与 base64 密文 |
| 点击 Decrypt | 读取同一文件并还原为原文 |
| 进程重启后 Decrypt 旧文件 | 抛出异常(key/iv 已重新生成,继承自上游限制) |
以下是操作的视屏,可以参考一下:
图一:demo 应用在 OpenHarmony 真机启动(API 24 / arm64),展示说明卡片、输入框和加解密按钮
图二:点击 Encrypt 后,界面展示密文 base64 内容、文件大小(44 bytes)和文件路径
图三:点击 Decrypt 后,界面展示 Decrypt OK: restored "Hello OpenHarmony!",加解密往返一致
检查要点:
- 加解密逻辑全部是纯 Dart 实现,通过
encrypt包的Encrypter(AES(key))完成 AES-128-CBC 运算,不经过插件自己的"file_encryptor"MethodChannel; - 鸿蒙侧的 ArkTS 插件类
FileEncryptorPlugin仅复刻了 Android/iOS 的模板契约(通道名file_encryptor、方法getPlatformVersion),保证插件注册链路完整; - 密文文件保存在应用沙箱目录内(
/data/storage/el2/base/files/flutter/),仅使用应用私有文件读写能力,不需要任何系统权限; - 完整实测过程见"六、运行与验证"。
HarmonyOS 技术点:应用沙箱与文件存储路径
鸿蒙系统的应用文件存储采用沙箱隔离机制。
getApplicationDocumentsDirectory()返回的路径(如/data/storage/el2/base/files/flutter/)位于应用私有沙箱内,其他应用无法直接访问。这一路径由path_provider_ohos插件提供,对应鸿蒙系统的context.filesDir属性。应用在沙箱内的文件读写不需要任何权限声明——系统自动保证隔离性。file_encryptor将密文文件test_file.aes写入此目录,既保证了数据隔离,又避免了权限申请的负担。
二、file_encryptor 是什么
file_encryptor 原库(GitHub m-abdulmonem/file-encryptor,版本 0.0.1)是一个轻量的 Flutter 文件加解密库。它使用 AES-128-CBC 算法对文本内容加密,以 base64 编码写入 .aes 文件,同时支持从该文件解密还原原文。鸿蒙适配版在其基础上新增了 OpenHarmony / HarmonyOS 平台支持:新增 ohos/ 平台工程与 ArkTS 插件类 FileEncryptorPlugin,通道名和方法名与 Android/iOS/macOS/Windows/Linux 完全一致。
为什么选择 AES-128-CBC?
AES(Advanced Encryption Standard)是对称加密的事实标准,128 表示密钥长度为 128 位(16 字节)。CBC(Cipher Block Chaining)模式通过将前一个密文块与当前明文块异或后再加密,使得相同的明文块在不同位置产生不同的密文,安全性高于 ECB 模式。
file_encryptor使用Key.fromSecureRandom(16)生成 128 位随机密钥,IV.fromSecureRandom(16)生成 16 字节随机初始向量,符合 AES-128-CBC 的标准要求。密文通过 base64 编码后写入文本文件,方便跨平台传输和存储。
几个对使用者友好的特点:
- 零权限:加解密操作仅使用应用沙箱内的文件读写能力,不需要在
module.json5中申请任何权限; - 纯 Dart 核心:AES 加解密逻辑全部通过
encrypt包在 Dart 层完成,不依赖任何原生平台 API,跨平台共享; - 极简 API:两个方法
encrypt(path, content)和decrypt(path),一行代码完成加密落盘或解密还原; - 跨平台覆盖广:支持 Android、iOS、macOS、Windows、Linux 和鸿蒙六端,同一套 Dart 代码在各端行为一致。
接口说明:
| 名称 | 描述 | 类型 | 参数类型 | 返回值 | 必填 | 鸿蒙平台支持 |
|---|---|---|---|---|---|---|
encrypt | 将文本加密后写入 path.aes 文件 | 方法 | String path, String content | Future<bool> | 是 | 是 |
decrypt | 读取 path.aes 文件并解密还原原文 | 方法 | String path | Future<String> | 是 | 是 |
HarmonyOS 技术点:
encrypt包与 Dart 原生加密
encrypt是 Flutter 生态中广泛使用的加密包(pub.dev),它提供了 AES、RSA、Salsa20 等算法的纯 Dart 实现。Encrypter(AES(key))创建一个 AES 加密器,encrypt(content, iv: iv)返回Encrypted对象,其.base64属性输出 base64 编码的密文字符串。decrypt64(base64String, iv: iv)则将 base64 密文解密还原为原文。这些运算全部在 Dart 虚拟机内完成,不调用任何系统级加密 API(如 Android 的javax.crypto或 iOS 的CommonCrypto),因此跨平台行为完全一致——鸿蒙端无需任何适配即可运行。
三、环境准备
本文所有实测均在以下环境完成:
| 项 | 版本 | 说明 |
|---|---|---|
| Flutter(ohos 版) | 3.44.9+ohos-0.0.1-canary1 | 主验证环境,真机实测 |
| DevEco Studio | 26.0.0(DS-261.23567.138.36.2600821) | 构建与签名 |
| 编译 SDK | 5.1.0(18) | 宿主工程 compatibleSdkVersion 同值 |
| 真机 | OpenHarmony 6.1.1.120 | API 24,arm64,设备 ID 4UQ9K25508013016 |
两点提醒:
- 编译鸿蒙目标需要使用 ohos 版 Flutter SDK,普通 Flutter SDK 无法编译 ohos 产物;
- 本库依赖
path_provider获取应用文档目录,鸿蒙端必须同时引入path_provider_ohos(插件已声明该依赖),否则调用getApplicationDocumentsDirectory()会抛MissingPluginException。
HarmonyOS 技术点:
path_provider与path_provider_ohos
path_provider是 Flutter 官方的路径查询插件,但它本身不包含鸿蒙平台实现。path_provider_ohos是 CPF-Flutter 社区提供的 OpenHarmony 适配版,通过注册path_provider的 MethodChannel 实现鸿蒙端路径查询。在pubspec.yaml中声明path_provider_ohos: ^2.2.1后,Flutter 构建工具自动将其鸿蒙平台实现注入GeneratedPluginRegistrant,getApplicationDocumentsDirectory()在鸿蒙端即可正常返回沙箱路径。如果只声明path_provider而不引入path_provider_ohos,鸿蒙端调用会抛MissingPluginException——这是因为path_provider的 MethodChannel 在鸿蒙端没有注册处理器。
四、引入依赖
进入工程目录,在 pubspec.yaml 中添加 git 依赖:
dependencies:
file_encryptor:
git:
url: https://github.com/m-abdulmonem/file-encryptor.git
# ref: 根据下方表格选择不同框架适配的 TAG 版本
ref: 0.0.1-ohos-1.0.0-beta.1
执行命令拉取依赖:
flutter pub get
TAG 命名规则:原库版本-ohos-版本号-beta.x。
| Flutter 框架版本 | TAG 名称 | 分支名 |
|---|---|---|
| 3.44 | 0.0.1-ohos-1.0.0-beta.1 | main |
说明:该 TAG 已在 3.44.9+ohos-0.0.1-canary1 真机上实测通过。原库的 Dart 层 API 与上游完全一致,适配过程对 Dart 代码零改动。插件同时声明了 path_provider_ohos: ^2.2.1 依赖,flutter pub get 会自动拉取鸿蒙端路径查询实现。
pubspec.yaml 中的 ohos 平台声明
适配后的
pubspec.yaml在flutter.plugin.platforms下新增了ohos配置项,同时声明了path_provider_ohos依赖:dependencies: path: ^1.8.1 path_provider: ^2.0.11 path_provider_ohos: ^2.2.1 # OpenHarmony path_provider 实现 encrypt: ^5.0.1 flutter: plugin: platforms: android: package: com.mabdulmonem.file_encryptor pluginClass: FileEncryptorPlugin ios: pluginClass: FileEncryptorPlugin ohos: pluginClass: FileEncryptorPlugin
pluginClass的值必须与 ArkTS 插件类getUniqueClassName()的返回值完全一致。path_provider_ohos的引入确保了getApplicationDocumentsDirectory()在鸿蒙端可用。
五、代码接入
5.1 导入库
import 'package:file_encryptor/file_encryptor.dart';
导入后即可使用 FileEncryptor 类和 encrypt() / decrypt() 方法,同时库还 re-export 了 path_provider 的 getApplicationDocumentsDirectory 和 path 包的 join 方法,方便一步获取路径。
5.2 加密文本到文件
final Directory appDocDir = await getApplicationDocumentsDirectory();
final String path = join(appDocDir.path, 'test_file');
final bool ok = await FileEncryptor().encrypt(path, 'Hello OpenHarmony!');
debugPrint('加密结果: $ok');
encrypt() 接受两个参数:path(不含扩展名的基础路径)和 content(要加密的文本),返回 Future<bool> 表示密文文件是否成功生成。以下是 Dart 层的实现代码,逐段解析:
class FileEncryptor {
static Key key = Key.fromSecureRandom(16);
static IV iv = IV.fromSecureRandom(16);
Encrypter get encrypter => Encrypter(AES(key));
Future<bool> encrypt(String path, String content) async {
final file = File("$path.aes");
file.writeAsString(encrypter.encrypt(content, iv: iv).base64);
return file.exists();
}
}
上述代码的核心逻辑分为三步。第一步是密钥与 IV 初始化:Key.fromSecureRandom(16) 生成 128 位(16 字节)随机密钥,IV.fromSecureRandom(16) 生成 16 字节随机初始向量。两者都是 static 字段,在类首次加载时生成一次,整个进程生命周期内保持不变。第二步是创建加密器:Encrypter(AES(key)) 使用 AES 算法和上一步的密钥构造加密器实例。第三步是加密并写入文件:encrypter.encrypt(content, iv: iv) 用 AES-128-CBC 算法和 IV 加密文本内容,返回 Encrypted 对象,.base64 属性输出 base64 编码的密文字符串。file.writeAsString() 将密文写入 $path.aes 文件(自动在路径后追加 .aes 扩展名),最后返回 file.exists() 确认文件是否成功生成。
HarmonyOS 技术点:AES-128-CBC 加密流程
AES-128-CBC 的加密流程为:将明文按 16 字节分块,第一个明文块与 IV(初始向量)异或后用密钥加密得到第一个密文块;第二个明文块与第一个密文块异或后加密得到第二个密文块,依此类推。最后一个块如果不足 16 字节,使用 PKCS7 填充补齐。
encrypt包的Encrypted.base64属性将密文字节做 base64 编码输出为字符串。实测中,20 字节的明文Hello OpenHarmony!经 PKCS7 填充后为 32 字节(两个块),base64 编码后为 44 字符——与真机实测的 44 bytes 文件大小完全吻合。
5.3 从文件解密还原文本
final String plainText = await FileEncryptor().decrypt(path);
debugPrint('解密结果: $plainText');
decrypt() 接受一个 path 参数(不含扩展名的基础路径),返回 Future<String> 为解密还原的原文。以下是实现代码:
Future<String> decrypt(String path) async {
final file = File("$path.aes");
final data = await file.readAsString();
return encrypter.decrypt64(data, iv: iv);
}
上述代码的逻辑分为两步。第一步读取密文文件:File("$path.aes").readAsString() 读取密文文件的 base64 字符串内容。第二步解密还原:encrypter.decrypt64(data, iv: iv) 将 base64 密文字符串解码为字节,用与加密时相同的密钥和 IV 解密还原原文。由于 key 和 iv 都是 static 字段,同一进程内加密后一定能解密还原。
解密的前提条件:解密要求目标文件
path.aes必须存在,且必须由同一进程(使用相同密钥与 IV)加密生成。文件不存在会抛FileSystemException;文件内容被篡改或使用不同密钥/IV 解密会抛FormatException或ArgumentError。业务层应在调用处使用 try/catch 处理这些异常。
5.4 与 path_provider 搭配使用
推荐结合 path_provider 获取应用文档目录,将密文文件保存在应用沙箱内:
final Directory appDocDir = await getApplicationDocumentsDirectory();
final String path = join(appDocDir.path, 'test_file');
getApplicationDocumentsDirectory() 在鸿蒙端返回 /data/storage/el2/base/files/flutter/ 等沙箱路径,由 path_provider_ohos 插件提供。join() 方法来自 path 包,用于跨平台拼接路径分隔符。file_encryptor 已经 re-export 了这两个依赖,导入 file_encryptor 即可直接使用。
为什么不直接用硬编码路径? 不同平台的文件系统路径格式不同(Android 用
/data/user/0/...,iOS 用.../Documents/,鸿蒙用/data/storage/el2/...),硬编码路径无法跨平台。path_provider根据当前平台返回正确的沙箱路径,path.join()正确拼接路径分隔符,确保同一代码在所有平台都能正确定位文件。
5.5 跨平台行为
同一套 API 在各端的行为完全一致,因为核心逻辑是纯 Dart 实现:
| 平台 | 加密算法 | 文件路径来源 | 权限要求 | Dart 层改动 |
|---|---|---|---|---|
| Android | AES-128-CBC(纯 Dart) | path_provider | 无 | 无 |
| iOS | AES-128-CBC(纯 Dart) | path_provider | 无 | 无 |
| macOS | AES-128-CBC(纯 Dart) | path_provider | 无 | 无 |
| Windows | AES-128-CBC(纯 Dart) | path_provider | 无 | 无 |
| Linux | AES-128-CBC(纯 Dart) | path_provider | 无 | 无 |
| OpenHarmony / HarmonyOS | AES-128-CBC(纯 Dart) | path_provider_ohos | 无 | 无 |
鸿蒙侧的 ArkTS 插件类 FileEncryptorPlugin 仅复刻了 Android/iOS 的模板契约(通道名 file_encryptor、方法 getPlatformVersion),Dart 层的加解密操作从未调用该通道——核心功能全部通过 encrypt 包在 Dart 层完成。
5.6 实战:给配置文件加加密存储
实际业务中常见的场景是:应用需要本地存储一些敏感配置(如 API 密钥、用户令牌),不能明文落盘。下面是一个可直接使用的封装:
import 'package:file_encryptor/file_encryptor.dart';
class SecureStorage {
static const _configFileName = 'app_config';
/// 加密保存配置
static Future<bool> save(String configJson) async {
final Directory appDocDir = await getApplicationDocumentsDirectory();
final String path = join(appDocDir.path, _configFileName);
return FileEncryptor().encrypt(path, configJson);
}
/// 解密读取配置
static Future<String> load() async {
final Directory appDocDir = await getApplicationDocumentsDirectory();
final String path = join(appDocDir.path, _configFileName);
try {
return FileEncryptor().decrypt(path);
} catch (e) {
debugPrint('解密失败: $e');
return '';
}
}
/// 检查配置文件是否存在
static Future<bool> exists() async {
final Directory appDocDir = await getApplicationDocumentsDirectory();
final String path = join(appDocDir.path, _configFileName);
return File('$path.aes').exists();
}
}
上述组件的设计思路是:通过 SecureStorage.save(jsonString) 将 JSON 配置加密后写入 app_config.aes 文件,通过 SecureStorage.load() 读取并解密还原。exists() 方法检查密文文件是否已存在,用于判断是否首次启动。load() 方法内部使用 try/catch 包裹解密操作——文件不存在或内容被篡改时返回空字符串而非崩溃。
密钥生命周期提醒:
FileEncryptor的密钥和 IV 是static字段,在进程首次加载时随机生成。这意味着进程重启后密钥会重新生成,之前加密的文件无法用新密钥解密。因此file_encryptor适合"单次会话内的临时保护"场景(如临时缓存、会话内数据交换),不适合"持久化加密存储"场景(如需要跨重启读取的配置文件)。持久化场景需要自行管理密钥的存储和恢复(见 FAQ Q2)。
六、运行与验证
以下为 demo 工程的真机实测记录。仓库中 example 的 Bundle Name 为 com.example.ohos_example_scaffold,签名配置使用 DevEco Studio 自动签名。
| 设备项 | 值 |
|---|---|
| 机型 | OpenHarmony 真机 |
| 设备 ID | 4UQ9K25508013016 |
| 系统版本 | OpenHarmony 6.1.1.120 |
| API 版本 | 24 |
| 架构 | arm64 |
HarmonyOS 技术点:FlutterAbility 与 EntryAbility
鸿蒙 Flutter 应用的入口 Ability 需要继承
FlutterAbility(由@ohos/flutter_ohos提供),而非标准的UIAbility。FlutterAbility内部封装了FlutterEngine的初始化、Surface 注册、路由管理等逻辑。宿主工程的EntryAbility只需重写configureFlutterEngine方法,在其中调用GeneratedPluginRegistrant.registerWith(flutterEngine)即可完成所有原生插件的注册:export default class EntryAbility extends FlutterAbility { configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) GeneratedPluginRegistrant.registerWith(flutterEngine) } }
6.1 验证一:构建与安装
构建 hap 后安装到真机并启动:
# 构建 hap(debug,含签名)
flutter build hap --debug
# 安装到真机
hdc install entry-default-signed.hap
# 启动 demo
hdc shell aa start -b com.example.ohos_example_scaffold -a EntryAbility
构建成功产出 entry-default-signed.hap,安装成功(install bundle successfully),启动成功(start ability successfully)。
签名提醒:OpenHarmony 设备仅信任 DevEco Studio 自动签名生成的调试证书。如果安装时报
fail to verify pkcs7 file,请使用 DevEco Studio 的自动签名配置(File > Project Structure > Signing Configs)生成签名材料后再构建 HAP。

6.2 验证二:插件注册
通过 hilog 确认插件注册:
hdc shell "hilog | grep -E 'FileEncryptorPlugin|file_encryptor|PathProvider'"
实测日志输出:
FlutterEngineCxnRegistry --> Adding plugin: FileEncryptorPlugin
FlutterEngineCxnRegistry --> Adding plugin: PathProviderPlugin
DartMessenger --> Setting handler for channel 'file_encryptor'
DartMessenger --> Setting handler for channel 'plugins.flutter.io/path_provider'
FileEncryptorPlugin 和 PathProviderPlugin 均成功注册到 Flutter 引擎。file_encryptor MethodChannel 和 plugins.flutter.io/path_provider MethodChannel 均已建立处理器——前者用于 getPlatformVersion 模板方法,后者用于 getApplicationDocumentsDirectory() 路径查询。
日志解读:
Adding plugin: FileEncryptorPlugin表示GeneratedPluginRegistrant.registerWith成功将加密插件实例添加到引擎。Adding plugin: PathProviderPlugin表示path_provider_ohos的路径查询插件也已注册——这保证了getApplicationDocumentsDirectory()在鸿蒙端可以正常返回沙箱路径。如果缺少PathProviderPlugin,路径查询会抛MissingPluginException。
6.3 验证三:加密操作
在 demo 界面的输入框中输入文本 Hello OpenHarmony!,点击"Encrypt"按钮。界面展示加密结果:
Encrypt OK: "Hello OpenHarmony!" → 44 bytes cipher file
Cipher content (base64): neLv/0I4Wn2M/BX6nm4IZ7Ch73Xf+uo2g3YXvd5xhIw=
File: /data/storage/el2/base/files/flutter/test_file.aes
加密成功,生成了 44 字节的 test_file.aes 文件,路径为 /data/storage/el2/base/files/flutter/test_file.aes。密文为 base64 字符串 neLv/0I4Wn2M/BX6nm4IZ7Ch73Xf+uo2g3YXvd5xhIw=。
文件大小分析:明文
Hello OpenHarmony!为 20 字节,经 PKCS7 填充后为 32 字节(两个 16 字节块),base64 编码后为 44 字符(32 × 4/3 向上取整 = 44,含 1 个=填充符)。实测文件大小 44 bytes 与计算值完全吻合。

6.4 验证四:解密操作
点击"Decrypt"按钮,从 test_file.aes 文件读取密文并解密还原。界面展示解密结果:
Decrypt OK: restored "Hello OpenHarmony!"
解密成功,还原的原文与输入的明文完全一致——加解密往返一致,验证了 AES-128-CBC 加解密的正确性。
实测结论:
| 验证点 | 结果 |
|---|---|
| 应用启动,Flutter 页面正常渲染 | 通过 |
输入文本并点击 Encrypt,生成 test_file.aes(44 bytes) | 通过 |
| 密文 base64 内容正确展示 | 通过 |
| 点击 Decrypt,还原原文,加解密往返一致 | 通过 |
| 密文文件保存在应用沙箱目录 | 通过 |
| 插件注册成功(FileEncryptorPlugin + PathProviderPlugin) | 通过 |
| 全程无需申请任何权限 | 通过 |
| Dart 层零改动 | 通过 |

七、工作原理
整个调用链路如下:
加密 (encrypt):
Dart: FileEncryptor().encrypt(path, 'Hello')
→ Encrypter(AES(key)).encrypt('Hello', iv: iv)
→ AES-128-CBC 运算(纯 Dart,encrypt 包)
→ Encrypted.base64 // base64 密文字符串
→ File('$path.aes').writeAsString(base64密文)
→ 应用沙箱文件写入(dart:io,无需权限)
→ return file.exists()
解密 (decrypt):
Dart: FileEncryptor().decrypt(path)
→ File('$path.aes').readAsString()
→ 读取 base64 密文字符串
→ Encrypter(AES(key)).decrypt64(base64密文, iv: iv)
→ AES-128-CBC 解密运算(纯 Dart,encrypt 包)
→ return 原文
插件模板通道(Dart 层未调用加解密,仅 getPlatformVersion):
Dart → 原生: MethodChannel('file_encryptor').invokeMethod('getPlatformVersion')
→ ArkTS: FileEncryptorPlugin.onMethodCall
→ result.success('OpenHarmony ' + deviceInfo.displayVersion)
关键区分:
file_encryptor通道 vsencrypt包file_encryptor 库涉及两条通信路径,它们的职责完全不同:
路径 归属 职责 Dart 层是否调用 encrypt包(纯 Dart 库)第三方 Dart 包 AES-128-CBC 加解密运算 是( Encrypter.encrypt/decrypt64)file_encryptorMethodChannel插件自定义 仅 getPlatformVersion(模板骨架)否 加解密运算走的是
encrypt包——这是一个纯 Dart 加密库,不涉及任何 MethodChannel 或原生平台 API。AES 运算全部在 Dart 虚拟机内完成。插件的file_encryptorMethodChannel 仅用于getPlatformVersion,是flutter create生成的模板骨架,Dart 层的加解密逻辑从未调用该通道。
7.1 Dart 层实现解析
库的 Dart 层只有一个文件 lib/file_encryptor.dart,包含一个 FileEncryptor 类和两个方法,逐段解析如下。
完整代码
import 'dart:io';
import 'package:encrypt/encrypt.dart';
export 'package:path_provider/path_provider.dart';
export 'package:path/path.dart';
class FileEncryptor {
static Key key = Key.fromSecureRandom(16);
static IV iv = IV.fromSecureRandom(16);
Encrypter get encrypter => Encrypter(AES(key));
Future<bool> encrypt(String path, String content) async {
final file = File("$path.aes");
file.writeAsString(encrypter.encrypt(content, iv: iv).base64);
return file.exists();
}
Future<String> decrypt(String path) async {
final file = File("$path.aes");
final data = await file.readAsString();
return encrypter.decrypt64(data, iv: iv);
}
}
key 和 iv 静态字段解析
static Key key = Key.fromSecureRandom(16);
static IV iv = IV.fromSecureRandom(16);
Key.fromSecureRandom(16) 生成 16 字节(128 位)的密码学安全随机密钥,IV.fromSecureRandom(16) 生成 16 字节的随机初始向量。两者都是 static 字段,在类首次被引用时初始化一次,整个进程生命周期内保持不变。这意味着同一进程内加密的数据可以用同一密钥和 IV 解密,但进程重启后密钥和 IV 会重新生成,之前加密的文件无法解密。
HarmonyOS 技术点:
Key.fromSecureRandom的随机源
Key.fromSecureRandom(16)底层调用 Dart 的Random.secure(),它使用操作系统的密码学安全随机数生成器(CSPRNG)。在鸿蒙系统上,Dart 虚拟机通过/dev/urandom或系统级安全随机源获取随机字节。生成的密钥在密码学上是安全的,不可预测。但由于密钥存储在 Dart 堆内存中(static字段),进程退出后密钥丢失——这是"会话内临时保护"设计,非"持久化加密"设计。
encrypter getter 解析
Encrypter get encrypter => Encrypter(AES(key));
这是一个 getter,每次访问时创建一个新的 Encrypter 实例,使用 static key 构造。AES(key) 创建 AES 加密算法的配置,Encrypter 封装了加密和解密方法。由于 key 是静态的,每次创建的 Encrypter 使用的密钥相同,加解密结果一致。
encrypt 方法解析
Future<bool> encrypt(String path, String content) async {
final file = File("$path.aes");
file.writeAsString(encrypter.encrypt(content, iv: iv).base64);
return file.exists();
}
encrypt 方法接受 path(基础路径,不含扩展名)和 content(明文文本)。在 path 后追加 .aes 扩展名创建 File 对象,调用 encrypter.encrypt(content, iv: iv) 加密内容,取 .base64 属性获取 base64 编码的密文字符串,通过 writeAsString 写入文件。注意 writeAsString 默认会覆盖已有文件内容。最后返回 file.exists() 确认文件是否成功生成。
writeAsString未 await 的原因:file.writeAsString(...)返回Future<File>,但代码中没有await——这意味着写入操作是"fire-and-forget"模式。在 Dart 的事件循环中,writeAsString会在当前微任务队列中调度写入操作,而file.exists()在下一个await点执行时,写入操作通常已经完成。但严格来说,这不是最佳实践——如果写入操作较慢(如大文件),exists()可能在写入完成前返回false。对于小文件(几十字节的密文),实践中不会出问题。
decrypt 方法解析
Future<String> decrypt(String path) async {
final file = File("$path.aes");
final data = await file.readAsString();
return encrypter.decrypt64(data, iv: iv);
}
decrypt 方法接受 path(基础路径),在 path 后追加 .aes 扩展名定位密文文件。await file.readAsString() 异步读取文件内容为字符串(base64 密文),encrypter.decrypt64(data, iv: iv) 将 base64 密文解密还原为原文。如果文件不存在,readAsString 会抛 FileSystemException;如果文件内容不是有效的 base64 或密文被篡改,decrypt64 会抛相应的异常。
re-export 声明解析
export 'package:path_provider/path_provider.dart';
export 'package:path/path.dart';
这两行 export 将 path_provider 的 getApplicationDocumentsDirectory 和 path 包的 join 函数统一导出。用户只需 import 'package:file_encryptor/file_encryptor.dart' 即可使用全部 API,无需额外导入 path_provider 和 path 包。
7.2 鸿蒙侧 ArkTS 插件实现
虽然加解密的核心功能是纯 Dart 实现,但为保证插件注册链路完整性(与 Android/iOS 行为一致),鸿蒙侧仍提供了 ArkTS 插件类。逐段解析如下。
第一段:导入与类声明
import deviceInfo from '@ohos.deviceInfo';
import { hilog } from '@kit.PerformanceAnalysisKit';
import {
FlutterPlugin,
FlutterPluginBinding,
MethodCall,
MethodCallHandler,
MethodChannel,
MethodResult,
} from '@ohos/flutter_ohos';
const TAG: string = 'FileEncryptorPlugin';
const CHANNEL_NAME: string = 'file_encryptor';
const DOMAIN: number = 0x0000;
export default class FileEncryptorPlugin implements FlutterPlugin, MethodCallHandler {
private channel: MethodChannel | null = null;
这段代码从 @ohos.deviceInfo 导入设备信息模块(用于 getPlatformVersion),从 @kit.PerformanceAnalysisKit 导入 hilog 日志模块(用于错误日志记录),从 @ohos/flutter_ohos 导入 Flutter 鸿蒙适配层的插件接口类型。FileEncryptorPlugin 类实现 FlutterPlugin(生命周期管理)和 MethodCallHandler(方法调用处理)两个接口——注意没有实现 AbilityAware,因为插件不需要 UIAbility 上下文(加解密通过纯 Dart 完成,不涉及系统级 API 调用)。
HarmonyOS 技术点:
@ohos.deviceInfo与displayVersion
@ohos.deviceInfo模块提供设备信息查询能力。deviceInfo.displayVersion返回系统的显示版本号(如HarmonyOS 6.1.1.120),语义上最接近 Android 的Build.VERSION.RELEASE和 iOS 的UIDevice.current.systemVersion。适配过程中,通过查询 SDK 的.d.ts类型声明文件(@ohos.deviceInfo.d.ts)确认displayVersion字段名——这是适配工作中的重要习惯:先查 SDK 声明文件确认 API 名称,而非凭记忆编写,避免使用不存在的字段名导致编译错误。
HarmonyOS 技术点:
hilog日志系统
hilog是鸿蒙系统的日志模块,来自@kit.PerformanceAnalysisKit工具包。hilog.error(domain, tag, format, ...args)用于输出错误级别日志。domain是日志域编号(0x0000 表示通用域),tag是日志标签,format支持%{public}s格式化占位符。与console.error相比,hilog的日志可以通过hdc shell hilog命令在真机上过滤查看,是鸿蒙原生开发的标准日志工具。
第二段:通道注册与生命周期管理
getUniqueClassName(): string {
return "FileEncryptorPlugin";
}
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;
}
}
getUniqueClassName() 返回 "FileEncryptorPlugin",需与 pubspec.yaml 中的 pluginClass 配置一致。onAttachedToEngine 在引擎加载插件时创建名为 "file_encryptor" 的 MethodChannel(与 Android/iOS/macOS 通道名一致)并设置方法调用处理器。onDetachedFromEngine 在引擎卸载插件时清理引用,将 channel 置为 null 防止内存泄漏。
第三段:方法分发与实现
onMethodCall(call: MethodCall, result: MethodResult): void {
try {
if (call.method == "getPlatformVersion") {
result.success("OpenHarmony " + deviceInfo.displayVersion);
} else {
result.notImplemented();
}
} catch (e) {
hilog.error(DOMAIN, TAG, 'onMethodCall failed, method=%{public}s, error=%{public}s',
call.method, JSON.stringify(e));
result.error("file_encryptor_error", `handle method "${call.method}" failed`, e);
}
}
onMethodCall 是方法调用的入口。当前仅处理 "getPlatformVersion" 方法,返回 "OpenHarmony " + deviceInfo.displayVersion。未知方法调用 result.notImplemented() 回复。所有逻辑包裹在 try/catch 中,异常时通过 hilog.error() 记录错误日志,并通过 result.error() 回传错误码 "file_encryptor_error"、错误消息和异常对象——不静默失败。
通道契约三端对照
契约项 Android (Kotlin) iOS (Swift) OHOS (ArkTS) 通道名 file_encryptorfile_encryptorfile_encryptor方法名 getPlatformVersiongetPlatformVersiongetPlatformVersion返回值 "Android ${Build.VERSION.RELEASE}""iOS " + systemVersion"OpenHarmony " + deviceInfo.displayVersion未知方法 result.notImplemented()FlutterMethodNotImplementedresult.notImplemented()错误处理 try/catch → result.error()try/catch → FlutterErrortry/catch → hilog.error+result.error()日志工具 Log.ehilog.error六端通道契约完全一致(含 macOS/Windows/Linux),仅返回值的前缀和系统版本来源不同。Dart 层的加解密操作从未调用
getPlatformVersion——它是模板骨架的一部分,保留它是为了与既有平台行为保持一致。
7.3 插件注册机制
鸿蒙侧的插件注册是自动完成的。Flutter 鸿蒙适配层在构建时扫描 pubspec.yaml 中的 ohos: pluginClass 配置,自动生成 GeneratedPluginRegistrant.ets 文件:
import { FlutterEngine, Log } from '@ohos/flutter_ohos';
import FileEncryptorPlugin from 'file_encryptor';
import PathProviderPlugin from 'path_provider_ohos';
export class GeneratedPluginRegistrant {
static registerWith(flutterEngine: FlutterEngine) {
try {
flutterEngine.getPlugins()?.add(new FileEncryptorPlugin());
flutterEngine.getPlugins()?.add(new PathProviderPlugin());
} catch (e) {
Log.e(TAG, "Tried to register plugins with FlutterEngine failed.");
}
}
}
注意 GeneratedPluginRegistrant 同时注册了 FileEncryptorPlugin 和 PathProviderPlugin——后者来自 path_provider_ohos 依赖。这保证了 getApplicationDocumentsDirectory() 在鸿蒙端可以正常返回沙箱路径。
整个注册链路为:
EntryAbility.configureFlutterEngine()
→ GeneratedPluginRegistrant.registerWith(flutterEngine)
→ flutterEngine.getPlugins().add(new FileEncryptorPlugin())
→ FileEncryptorPlugin.onAttachedToEngine(binding)
→ new MethodChannel(messenger, "file_encryptor")
→ setMethodCallHandler(this)
→ flutterEngine.getPlugins().add(new PathProviderPlugin())
→ PathProviderPlugin.onAttachedToEngine(binding)
→ new MethodChannel(messenger, "plugins.flutter.io/path_provider")
→ setMethodCallHandler(...)
注册完成后,file_encryptor 和 plugins.flutter.io/path_provider 两条 MethodChannel 都已就绪。但 Dart 层的 FileEncryptor().encrypt() 和 FileEncryptor().decrypt() 方法不通过 file_encryptor 通道发送请求——它们调用的是 encrypt 包的纯 Dart API 和 dart:io 的文件 I/O。
HarmonyOS 技术点:HAR 模块
file_encryptor的鸿蒙侧原生代码以 HAR(Harmony Archive)模块形式打包,类似于 Android 的 AAR。HAR 模块在module.json5中声明"type": "har",可以被宿主工程通过oh-package.json5依赖引入。Flutter 的 ohos 适配层会自动在宿主工程的GeneratedPluginRegistrant中注册插件类,无需手动编写注册代码。
以下是操作的视屏,可以参考一下:
八、常见问题
Q1:getApplicationDocumentsDirectory() 抛 MissingPluginException 怎么办?
原因是未引入 path_provider_ohos 依赖。path_provider 本身不包含鸿蒙平台实现,需要通过 path_provider_ohos 提供 OpenHarmony 端的路径查询能力。file_encryptor 的 pubspec.yaml 已声明 path_provider_ohos: ^2.2.1 依赖,执行 flutter pub get 后重新构建即可。如果仍然报错,检查 GeneratedPluginRegistrant.ets 是否包含 PathProviderPlugin 注册行。
Q2:进程重启后之前加密的文件无法解密是怎么回事?
这是 file_encryptor 的已知设计限制。密钥 Key.fromSecureRandom(16) 和 IV IV.fromSecureRandom(16) 是 static 字段,在类首次加载时随机生成。进程重启后类重新加载,密钥和 IV 重新随机生成,与之前加密时使用的密钥不同,因此无法解密历史文件。file_encryptor 适合"单次会话内的临时保护"场景(如临时缓存、会话内数据交换),不适合"持久化加密存储"场景。持久化场景需要自行管理密钥的存储和恢复——例如将密钥持久化到安全存储(如 flutter_secure_storage),在每次启动时恢复密钥。
Q3:安装 HAP 时报 fail to verify pkcs7 file 怎么办?
这是签名信任链问题。OpenHarmony 设备仅信任 DevEco Studio 自动签名生成的调试证书(default_ohos_*.p12/.cer/.p7b),而不信任 SDK 自带的 OpenHarmony.p12 官方调试链。解决方法:使用 DevEco Studio 的自动签名配置(File > Project Structure > Signing Configs)生成签名材料后再构建 HAP。确保 build-profile.json5 中的 signingConfigs 引用的是 DevEco 自动生成的证书路径。
Q4:加密后的文件大小为什么比明文大?
这是 AES-128-CBC 加密和 base64 编码的双重膨胀。以 20 字节明文为例:PKCS7 填充将明文补齐到 16 字节的倍数(20 → 32 字节,两个块),base64 编码将 3 字节扩展为 4 字符(32 → 44 字符,含 1 个 = 填充符)。因此 20 字节明文最终生成 44 字节的密文文件。膨胀率约为 2.2 倍(44/20),对于小文件来说比例较高,但对于大文件(如 KB 级以上),膨胀率趋近于 4/3 × 16/16 ≈ 1.33 倍。
Q5:file_encryptor 通道和 encrypt 包有什么区别?
file_encryptor MethodChannel 是插件自定义的通道,仅用于 getPlatformVersion 模板方法,Dart 层的加解密操作从未调用该通道。encrypt 包是一个纯 Dart 加密库,提供 AES、RSA 等算法的实现,file_encryptor 的加解密逻辑全部通过 encrypt 包在 Dart 虚拟机内完成,不涉及 MethodChannel 或原生平台 API。因此鸿蒙端无需为加解密逻辑编写任何原生代码——ArkTS 插件类仅为保证注册链路完整性。
Q6:可以用 file_encryptor 加密图片或其他二进制文件吗?
当前版本不支持。encrypt() 方法的 content 参数类型为 String,encrypter.encrypt() 方法接受文本输入。如果要加密二进制数据,需要先将二进制转为 base64 字符串再传入 encrypt(),或自行修改 Dart 层代码使用 encrypt 包的 encryptBytes() 方法处理 Uint8List 输入。由于库的设计目标是"文本加密落盘",二进制场景建议直接使用 encrypt 包而非 file_encryptor。
九、结语
回顾一下:在 pubspec.yaml 中以 git TAG 引入 file_encryptor,调用 FileEncryptor().encrypt(path, content) 将文本 AES-128-CBC 加密后以 base64 写入 .aes 文件,调用 FileEncryptor().decrypt(path) 读取文件并解密还原原文。核心功能全部是纯 Dart 实现,通过 encrypt 包完成 AES 运算,不经过插件自定义通道。零权限、应用沙箱内文件 I/O、Dart 层零改动,已在 OpenHarmony 6.1.1.120 真机(API 24 / arm64)完整实测。需注意密钥为进程内静态随机值,进程重启后无法解密历史文件,仅适合会话内临时保护场景。
总结对比
接口 功能 实现方式 底层依赖 权限要求 encrypt()加密文本写入 .aes文件纯 Dart( encrypt包 +dart:io)encrypt包 +path_provider_ohos无 decrypt()读取 .aes文件解密还原纯 Dart( encrypt包 +dart:io)encrypt包 +path_provider_ohos无
路径 归属 用途 Dart 层是否调用 encrypt包纯 Dart 库 AES-128-CBC 加解密运算 是( Encrypter.encrypt/decrypt64)file_encryptorMethodChannel插件自定义 仅 getPlatformVersion否(模板骨架) plugins.flutter.io/path_providerpath_provider_ohos 应用沙箱路径查询 是( getApplicationDocumentsDirectory)
平台 加密算法 路径来源 权限 Dart 层改动 Android AES-128-CBC(纯 Dart) path_provider无 无 iOS AES-128-CBC(纯 Dart) path_provider无 无 OHOS AES-128-CBC(纯 Dart) path_provider_ohos无 无 核心要点:两个方法,纯 Dart AES 加解密,零权限沙箱文件 I/O,密钥进程内静态随机,Dart 层零改动。
使用中发现任何问题,欢迎到配套仓库提 Issue,也欢迎发 PR 共建。
相关链接
欢迎加入 CPF-Flutter 鸿蒙社区,社区入口、环境搭建指南和本文相关链接统一放在这里:
更多推荐




所有评论(0)