flutter_des 适配 HarmonyOS 实战:以 DES 加密解密为例

欢迎大家加入开源鸿蒙跨平台社区

image-20260228133645814

前言

背景介绍:flutter_des 是一个跨平台的 DES 加密解密 Flutter 插件,支持 Android、iOS、macOS 平台,可在各平台获得一致的加密结果。随着 HarmonyOS 生态的发展,将 Flutter 插件适配到 HarmonyOS 平台成为开发者关注的重点。

适配目标:本文以 flutter_des 为例,详细介绍如何将基于 MethodChannel 的 Flutter 插件适配到 HarmonyOS 平台,重点解决 HarmonyOS 原生不支持 DES 算法、ArkTS 严格模式限制等实际问题。

文章价值:为 Flutter 插件开发者提供一份可复用的 HarmonyOS 适配实战指南,涵盖环境配置、架构对比、核心实现、常见问题及最佳实践。


一、背景介绍

1.1 插件概述

flutter_des 是 OctMon 开源的 Flutter DES 加密解密插件,采用 DES/CBC 模式,在 Android 使用 PKCS5Padding,在 iOS/macOS 使用 kCCOptionPKCS7Padding(8 字节分组时两者等效),确保跨平台加密结果一致。

1.2 插件功能

功能 说明
encrypt 加密为字节数组
encryptToHex 加密为十六进制字符串
encryptToBase64 加密为 Base64 字符串
decrypt 从字节数组解密
decryptFromHex 从十六进制字符串解密
decryptFromBase64 从 Base64 字符串解密

1.3 适配目标

  • 在 HarmonyOS 平台实现与 Android/iOS 一致的 DES 加密解密结果
  • 保持 Dart 层 API 不变,无需修改调用方代码
  • 解决 HarmonyOS 原生 cryptoFramework 不支持 DES 的问题
  • 满足 ArkTS 严格模式的语法和类型约束

二、环境准备与项目初始化

2.1 环境要求

工具 版本要求 说明
Flutter SDK 3.35.8-ohos-0.0.2+ 支持 HarmonyOS 的 Flutter 版本
Dart SDK 3.9.2+ 随 Flutter SDK 一起安装
DevEco Studio 6.1.0+ HarmonyOS 官方 IDE
HarmonyOS SDK 5.1.0(18)+ HarmonyOS 开发工具包
ohpm 最新版 OpenHarmony 包管理器

2.2 创建 HarmonyOS 平台支持

在项目根目录执行:

flutter create . --template=plugin --platforms=ohos

执行后会产生以下目录结构:

项目根目录/
├── ohos/
│   ├── src/main/ets/components/plugin/
│   │   └── FlutterDesPlugin.ets    # 主要实现文件
│   ├── src/main/module.json5
│   ├── index.ets
│   ├── oh-package.json5
│   └── build-profile.json5
└── example/
    └── ohos/                       # 示例项目

2.3 配置 pubspec.yaml

pubspec.yamlflutter.plugin.platforms 部分添加 ohos 配置:

flutter:
  plugin:
    platforms:
      android:
        package: com.octmon.flutter_des
        pluginClass: FlutterDesPlugin
      ios:
        pluginClass: FlutterDesPlugin
      macos:
        pluginClass: FlutterDesPlugin
      ohos:
        pluginClass: FlutterDesPlugin   # 必须与实现类名一致

三、HarmonyOS Flutter 插件架构

3.1 插件生命周期

flutter_des 属于纯 MethodChannel 插件,不涉及 UI 或窗口操作,因此只需实现 FlutterPluginMethodCallHandler 两个接口,无需实现 AbilityAware

Flutter Engine 启动
    ↓
onAttachedToEngine() → 创建 MethodChannel,注册 MethodCallHandler
    ↓
onMethodCall() → 处理 encrypt/decrypt 等方法调用
    ↓
Flutter Engine 销毁
    ↓
onDetachedFromEngine() → 解绑 MethodChannel

3.2 关键接口详解

接口 是否必需 说明
FlutterPlugin 必需 绑定/解绑 Flutter Engine
MethodCallHandler 必需 处理方法调用
AbilityAware 可选 仅当需要访问 UIAbility/窗口时实现,flutter_des 不需要

四、Android vs HarmonyOS 实现对比

4.1 架构差异对比

特性 Android HarmonyOS 说明
加密库 javax.crypto.Cipher @ohos/crypto-js HarmonyOS 原生不支持 DES,需用第三方库
算法 DES/CBC/PKCS5Padding DES/CBC/Pkcs7 PKCS5 与 PKCS7 在 8 字节分组时等效
参数访问 call.arguments call.args OHOS MethodCall 使用 args 属性
语言 Java ArkTS ArkTS 有严格模式限制

4.2 代码结构对比

Android 实现(Java)
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
  ArrayList arguments = (ArrayList) call.arguments;  // 注意:arguments
  String key = (String) arguments.get(1);
  String iv = (String) arguments.get(2);
  switch (call.method) {
    case "encrypt":
      result.success(encrypt((String) arguments.get(0), key, iv));
      break;
    // ...
  }
}

private static byte[] encrypt(String data, String key, String iv) {
  Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
  cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv.getBytes()));
  return cipher.doFinal(data.getBytes());
}
HarmonyOS 实现(ArkTS)
onMethodCall(call: MethodCall, result: MethodResult): void {
  let args = call.args as Array<Object>;  // 注意:args,不是 arguments
  let key = args[1] as string;
  let iv = args[2] as string;
  switch (call.method) {
    case "encrypt":
      result.success(this.doEncrypt(args[0] as string, key, iv));
      break;
    // ...
  }
}

private doEncrypt(data: string, key: string, iv: string): Uint8Array | null {
  let keyWA = CryptoJS.enc.Utf8.parse(key);
  let ivWA = CryptoJS.enc.Utf8.parse(iv);
  let encrypted = CryptoJS.DES.encrypt(data, keyWA, { iv: ivWA });
  return this.wordArrayToBytes(encrypted.ciphertext);
}

4.3 关键差异总结

  1. 参数访问:OHOS 使用 call.args,Android 使用 call.arguments
  2. 加密实现:Android 用原生 Cipher,OHOS 用 @ohos/crypto-js
  3. 类型系统:ArkTS 严格模式禁止无类型对象字面量、禁止将命名空间赋给变量

五、核心实现详解

5.1 完整的 HarmonyOS 实现

以下是完整的 FlutterDesPlugin.ets 实现,包含详细注释:

import {
  FlutterPlugin,
  FlutterPluginBinding,
  MethodCall,
  MethodCallHandler,
  MethodChannel,
  MethodResult,
} from '@ohos/flutter_ohos';
import { CryptoJS } from '@ohos/crypto-js';

/**
 * FlutterDesPlugin - DES 加密解密 HarmonyOS 实现
 * 实现 FlutterPlugin 和 MethodCallHandler,无需 AbilityAware
 */
export default class FlutterDesPlugin implements FlutterPlugin, MethodCallHandler {
  private channel: MethodChannel | null = null;

  getUniqueClassName(): string {
    return "FlutterDesPlugin";  // 必须与 pubspec.yaml 中 pluginClass 一致
  }

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

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

  onMethodCall(call: MethodCall, result: MethodResult): void {
    try {
      // 关键:OHOS 使用 call.args,不是 call.arguments
      let args = call.args as Array<Object>;
      if (args == null || args.length < 3) {
        result.error("INVALID_ARGS", "Arguments must be [data, key, iv]", null);
        return;
      }

      let key = args[1] as string;
      let iv = args[2] as string;

      switch (call.method) {
        case "encrypt":
          result.success(this.doEncrypt(args[0] as string, key, iv));
          break;
        case "encryptToHex":
          result.success(this.doEncryptToHex(args[0] as string, key, iv));
          break;
        case "decrypt":
          result.success(this.doDecryptBytes(args[0] as Uint8Array, key, iv));
          break;
        case "decryptFromHex":
          result.success(this.doDecryptFromHex(args[0] as string, key, iv));
          break;
        default:
          result.notImplemented();
      }
    } catch (err) {
      result.error("CRYPTO_ERROR", `Error: ${(err as Error).message}`, null);
    }
  }

  // 加密:CryptoJS 默认 CBC + Pkcs7,与 Android PKCS5Padding 等效
  private doEncrypt(data: string, key: string, iv: string): Uint8Array | null {
    try {
      let keyWA = CryptoJS.enc.Utf8.parse(key);
      let ivWA = CryptoJS.enc.Utf8.parse(iv);
      let encrypted = CryptoJS.DES.encrypt(data, keyWA, { iv: ivWA });
      return this.wordArrayToBytes(encrypted.ciphertext);
    } catch (e) {
      return null;
    }
  }

  // 解密:通过 HEX→Base64 转换避免 ArkTS 创建 CipherParams 对象字面量
  private doDecryptBytes(data: Uint8Array, key: string, iv: string): string | null {
    try {
      let hexStr = this.toHexString(data);
      let wordArray = CryptoJS.enc.Hex.parse(hexStr);
      let base64Str = CryptoJS.enc.Base64.stringify(wordArray);
      let keyWA = CryptoJS.enc.Utf8.parse(key);
      let ivWA = CryptoJS.enc.Utf8.parse(iv);
      let decrypted = CryptoJS.DES.decrypt(base64Str, keyWA, { iv: ivWA });
      return decrypted.toString(CryptoJS.enc.Utf8);
    } catch (e) {
      return "";
    }
  }

  // WordArray 转 Uint8Array(CryptoJS 内部使用 32 位字数组)
  private wordArrayToBytes(wa: CryptoJS.lib.WordArray): Uint8Array {
    let words = wa.words;
    let sigBytes = wa.sigBytes;
    let u8 = new Uint8Array(sigBytes);
    let offset = 0;
    for (let i = 0; i < words.length && offset < sigBytes; i++) {
      let word = words[i];
      u8[offset++] = (word >>> 24) & 0xff;
      if (offset < sigBytes) u8[offset++] = (word >>> 16) & 0xff;
      if (offset < sigBytes) u8[offset++] = (word >>> 8) & 0xff;
      if (offset < sigBytes) u8[offset++] = word & 0xff;
    }
    return u8;
  }

  private toHexString(bytes: Uint8Array): string {
    let hex = "";
    for (let i = 0; i < bytes.length; i++) {
      let h = (bytes[i] & 0xff).toString(16);
      hex += h.length === 1 ? "0" + h : h;
    }
    return hex.toUpperCase();  // 与 Android byte2hex 保持一致
  }
}

5.2 关键实现点深度解析

5.2.1 使用 @ohos/crypto-js 替代原生加密

原因:HarmonyOS 原生 @ohos.security.cryptoFramework 不支持 DES,仅支持 3DES 及以上算法。

方案:在 oh-package.json5 中添加依赖:

{
  "dependencies": {
    "@ohos/crypto-js": "^2.0.4"
  }
}
5.2.2 ArkTS 严格模式注意事项
  • 禁止 let crypto = CryptoJS(arkts-no-ns-as-obj):命名空间不能赋给变量,需直接调用 CryptoJS.DES.encrypt(...)
  • 允许 { iv: ivWA } 作为函数参数:仅包含显式声明属性的对象字面量可接受
  • 解密:使用 Base64 字符串传入 CryptoJS.DES.decrypt,避免创建 CipherParams 对象字面量
5.2.3 错误处理

所有 onMethodCall 逻辑包裹在 try-catch 中,异常时调用 result.error() 返回错误码和消息,便于 Dart 层统一处理。


六、适配步骤总结

6.1 完整适配 Checklist

  • 执行 flutter create . --template=plugin --platforms=ohos
  • 在 pubspec.yaml 中添加 ohos.pluginClass
  • 在 oh-package.json5 中添加 @ohos/crypto-js 依赖
  • 实现 FlutterDesPlugin.ets(FlutterPlugin + MethodCallHandler)
  • 使用 call.args 获取参数
  • 实现 encrypt、encryptToHex、decrypt、decryptFromHex 四个方法
  • 执行 ohpm installflutter pub get
  • 真机签名并运行 flutter run 验证

6.2 pubspec.yaml 配置详解

ohos:
  pluginClass: FlutterDesPlugin  # 必须与 getUniqueClassName() 返回值完全一致

6.3 适配流程图

开始 → 创建 OHOS 平台 → 配置 pubspec.yaml → 添加 crypto-js 依赖
  → 实现 FlutterDesPlugin.ets → 处理 ArkTS 限制 → 真机测试 → 完成

七、常见问题与解决方案

7.1 Property ‘arguments’ does not exist on type ‘MethodCall’

错误信息Did you mean 'argument'?

原因:OHOS Flutter 的 MethodCall 使用 args 属性,而非 arguments

解决方案

// 错误
const args = call.arguments;

// 正确
const args = call.args as Array<Object>;

调试技巧:查阅 @ohos/flutter_ohos 中 MethodCall 的类型定义,确认属性名。

7.2 Object literal must correspond to some explicitly declared class or interface

错误信息arkts-no-untyped-obj-literals

原因:ArkTS 禁止无类型对象字面量作为类字段初始值或独立变量。

解决方案:避免 = {} 初始化,使用 { iv: ivWA } 作为函数参数直接传递(CipherOption 接口已声明 iv 属性)。

7.3 Namespaces cannot be used as objects (arkts-no-ns-as-obj)

错误信息arkts-no-ns-as-obj

原因:将 CryptoJS 命名空间赋给变量,如 let crypto = CryptoJS

解决方案:直接调用 CryptoJS.DES.encrypt(...)CryptoJS.enc.Utf8.parse(...) 等,不要将命名空间赋给变量。

7.4 HarmonyOS 原生不支持 DES

问题cryptoFramework.createCipher('DES|CBC|PKCS7') 报错或不支持。

原因:HarmonyOS 出于安全考虑,cryptoFramework 仅支持 3DES 及以上算法。

解决方案:使用 @ohos/crypto-js 第三方库实现 DES,确保与 Android/iOS 结果一致。

7.5 解密时 CipherParams 对象字面量报错

错误信息CipherParams.create({ ciphertext: wordArray }) 触发对象字面量限制。

解决方案:将密文转为 Base64 字符串,直接调用 CryptoJS.DES.decrypt(base64Str, key, { iv }),decrypt 支持字符串输入。

7.6 Plugin not found: FlutterDesPlugin

原因pluginClassgetUniqueClassName() 返回值不一致,或大小写错误。

解决方案:确保 pubspec.yaml 中 pluginClass: FlutterDesPlugin 与实现类 getUniqueClassName(): string { return "FlutterDesPlugin"; } 完全一致。


八、最佳实践与开发建议

8.1 代码组织最佳实践

  • 将加密/解密逻辑封装为私有方法(doEncrypt、doDecryptBytes 等),便于维护和单元测试
  • WordArray 与 Uint8Array 的转换单独成方法,避免重复代码

8.2 错误处理最佳实践

  • 所有 onMethodCall 逻辑包裹在 try-catch 中
  • 使用 result.error("ERROR_CODE", "message", null) 返回结构化错误
  • 加密失败返回 null,解密失败返回空字符串,与 Android 行为保持一致

8.3 性能优化建议

  • @ohos/crypto-js 为纯 JS 实现,大数据量加密时可能较慢,可考虑在 Dart 层做分块或异步处理
  • 密钥和 IV 的 UTF8 解析可缓存(若同一会话多次调用)

8.4 测试建议

  • 使用与 Android/iOS 相同的测试向量,验证加密结果一致
  • 覆盖空字符串、特殊字符、长文本等边界情况

8.5 调试技巧

  • 在 DevEco Studio 中打开 example/ohos 项目,可单步调试 ArkTS 代码
  • 使用 console.info() 输出中间变量(需在调试构建中)
  • 对比 Android 与 OHOS 的 hex 输出,排查编码差异

九、总结

9.1 核心要点回顾

  1. 参数访问:OHOS 使用 call.args,不是 call.arguments
  2. 加密库:HarmonyOS 原生不支持 DES,使用 @ohos/crypto-js
  3. ArkTS 限制:禁止命名空间赋变量、注意对象字面量类型
  4. 接口选择:纯 MethodChannel 插件只需 FlutterPlugin + MethodCallHandler,无需 AbilityAware

9.2 适配价值

  • 扩展 flutter_des 至 HarmonyOS 生态,实现四端一致加密
  • 为其他 MethodChannel 类插件提供可复用的适配模式
  • 积累 ArkTS 与第三方 JS 库集成的实战经验

9.3 后续建议

  • 在真机上完成完整功能测试
  • 编写 README.OpenHarmony_CN.md / README.OpenHarmony.md 适配说明
  • 考虑在安全性要求高的场景推荐使用 AES 替代 DES

9.4 学习资源

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐