1. 项目概述与背景

最近在搞一个鸿蒙原生应用的项目,里面有个模块涉及到金融级别的离线数据加密。需求很明确:数据要在本地完成加密,然后安全地上传到服务端,整个链路不能有明文泄露的风险。团队之前的技术栈是Flutter,所以自然就想到了用现成的 dart_des 库来处理DES和3DES加解密。但问题来了,这个库是纯Dart写的,依赖了Dart原生的 dart:typed_data 等库进行位操作,在鸿蒙(HarmonyOS)的ArkTS环境下直接跑不起来,一编译就报各种“找不到符号”的错误。

这其实是一个典型的跨平台迁移问题。Flutter的Dart代码在鸿蒙上水土不服,核心原因在于运行时环境和底层API的差异。鸿蒙的ArkTS虽然语法上像TypeScript,但其运行时和FFI(外部函数接口)机制与Dart VM完全不同。 dart_des 库内部那些精巧的位运算、字节数组处理,在鸿蒙上需要找到对等的实现方式。这个适配过程,不仅仅是简单的“翻译”,更是对算法原理和鸿蒙安全框架的一次深度理解。

所以,就有了这篇指南。我会带你完整走一遍将 dart_des 这个纯Dart三方库适配到鸿蒙ArkTS环境的过程。目标不仅仅是让代码能跑起来,更要确保在鸿蒙系统上,DES/3DES算法的执行效率、内存安全以及密钥管理都能符合生产级应用,特别是金融交易、硬件通信这类高敏感场景的要求。你会发现,适配的过程,也是加固你应用安全防线的过程。

2. 核心需求与适配目标解析

2.1 为什么是DES/3DES?

在当今AES一统天下的时代,为什么我们还要折腾DES和3DES?这恰恰是很多金融、硬件协议领域的现状。不是不想用AES,而是历史包袱和兼容性要求使然。

很多传统的POS机、金融终端设备,其硬件安全模块(HSM)或通信协议固化了DES/3DES算法。你去改造这些硬件成本极高。同样,一些遗留的银行间报文交换系统,其数据加密标准可能仍然指定使用3DES。我们的应用如果需要与这些系统对接,就必须实现对应的算法。 dart_des 库提供了纯Dart的实现,给了Flutter应用一个轻量级、不依赖原生平台的解决方案,这在跨平台初期是个优势。

但它的局限性也很明显:性能不如原生实现,且在鸿蒙上无法直接使用。我们的适配,就是要保留其算法逻辑的正确性,同时将其“鸿蒙化”,利用鸿蒙的API获得更好的性能和安全性。

2.2 鸿蒙化适配的具体目标

我们的适配工作不能是黑盒,必须明确目标:

  1. 功能对等 :确保在鸿蒙ArkTS上实现的DES/3DES加解密,与原始 dart_des 库的输出结果完全一致。这是底线,任何偏差都会导致数据无法解密,业务中断。
  2. API兼容 :尽可能保持与原库类似的API设计,降低业务代码的改造成本。例如,保持 DES TripleDES 这样的类,以及 encrypt decrypt 这样的方法名。
  3. 性能达标 :利用鸿蒙的 util 加密套件或高效的 ArrayBuffer 操作,确保加解密速度能满足高频交易或实时数据流处理的要求。纯Dart解释执行的性能在鸿蒙上可能成为瓶颈。
  4. 安全增强 :结合鸿蒙的安全能力,比如密钥的安全存储(通过 @ohos.security.cryptoFramework )、真随机数生成等,弥补纯软件实现可能存在的安全短板。
  5. 工程化集成 :将适配后的代码打包成标准的HarmonyOS HAR(HarmonyOS Archive)包,方便在多个鸿蒙项目中复用,并编写清晰的文档和单元测试。

3. 原 dart_des 库原理与鸿蒙环境差异

3.1 DES算法核心与 dart_des 实现剖析

要适配,先得读懂它。DES是一种对称分组密码算法,密钥长度64位(实际有效56位),分组长度64位。其核心在于16轮的Feistel网络结构,每一轮都包含扩展置换、S盒替换、P盒置换等操作。 dart_des 库用Dart优雅地实现了这一切。

它通常有几个关键类:一个 DES 类负责算法的核心流程,一个 DESMode (如CBC, ECB)类负责分组模式,一个 PKCS7Padding 类负责填充。它的实现大量使用了Dart的 Uint8List (字节数组)和位操作符( & , | , << , >> )来进行比特级的计算。例如,生成子密钥时的循环左移,或者S盒查找时的行列计算,都是通过位操作完成的。

// 类似dart_des中的位操作片段(示意)
int _permute(int block, List<int> table, int length) {
  int result = 0;
  for (int i = 0; i < length; i++) {
    if ((block >> (table[i] - 1)) & 1 == 1) {
      result |= (1 << (length - 1 - i));
    }
  }
  return result;
}

这种实现方式清晰、可移植,但也高度依赖Dart语言对整型位操作的支持和运行时性能。

3.2 鸿蒙ArkTS环境的挑战

鸿蒙的ArkTS/JS运行时环境与Dart VM有本质不同:

  1. 类型系统 :ArkTS/JS的数字类型是双精度浮点数,没有原生的64位整数类型。而DES算法大量涉及64位比特块的运算。直接移植会导致精度丢失和溢出错误。
  2. 位操作 :虽然TypeScript/ArkTS也支持位操作符,但它们操作的是32位有符号整数。对于超过32位的DES数据块,无法直接进行位运算。
  3. 加密API :鸿蒙系统提供了 @ohos.security.cryptoFramework 能力,其中包含了对称加密的实现。这是一个更优的选择,但需要确认其是否直接支持DES/3DES算法,以及其模式、填充方式是否与我们的需求匹配。
  4. 性能考量 :在JS层用数组模拟大整数位运算,性能开销巨大,不适合高频加密场景。

因此,我们的适配策略需要分层次考虑:优先尝试使用鸿蒙原生加密框架;如果原生不支持或无法满足特定模式,则需要在ArkTS层实现一个兼容层,用 Uint8Array (对应 Uint8List )来模拟比特操作,或者寻找更底层的Native API(通过NAPI)来实现高性能核心。

注意 :在金融等高安全场景,使用未经充分审计的、自行实现的加密算法存在风险。如果鸿蒙原生加密框架支持DES/3DES,应优先采用,因为它通常经过更严格的安全认证并与硬件安全能力结合。

4. 适配方案设计与技术选型

4.1 方案一:基于鸿蒙原生CryptoFramework(推荐)

这是最安全、性能最好的路径。鸿蒙的 cryptoFramework 提供了对称加密的能力。我们首先需要查验其支持的算法。

// 示例:检查系统能力(伪代码)
import cryptoFramework from '@ohos.security.cryptoFramework';

async function checkDesSupport() {
  try {
    // 尝试创建DES加密器
    let cipherAlgName = 'DES_ECB|PKCS7'; // 尝试不同的算法名称组合
    let generator = cryptoFramework.createSymKeyGenerator('DES');
    // 如果成功,说明系统支持DES算法
    console.info('DES is supported by cryptoFramework.');
    // 进一步检查密钥长度和模式
    let supportedModes = generator.getSupportedModes();
    let supportedPaddings = generator.getSupportedPaddings();
    return true;
  } catch (error) {
    console.error(`DES may not be supported: ${error.message}`);
    return false;
  }
}

如果 cryptoFramework 支持 DES 3DES (算法名可能是 DES 3DES DESede 等),那么适配工作就简化为“桥接”层:我们创建一个ArkTS类,其内部调用 cryptoFramework 的API,但对外暴露与 dart_des 相似的接口。

优势

  • 高性能 :底层由C/C++实现,可能还有硬件加速。
  • 高安全 :受益于系统级的安全设计和认证。
  • 维护省心 :跟随鸿蒙系统升级。

劣势

  • 可能受系统版本限制,旧版本鸿蒙可能不支持。
  • 支持的加密模式(如CBC, ECB)和填充方式(如PKCS7)可能不全,需要验证。

4.2 方案二:纯ArkTS兼容层实现

如果原生框架不支持,或者我们需要完全控制算法的具体实现(例如实现一个非标准的变种),那就需要在ArkTS层重新实现算法。

核心思路是: 使用 Uint8Array (字节数组)作为基本数据容器,所有比特操作都通过字节数组的索引和位掩码来完成,避免直接使用JS的大数位运算。

例如,实现64位块的循环左移:

function circularLeftShift28(high28: number, low28: number, shiftBits: number): [number, number] {
  // high28和low28各代表28位,存储在number中(JS number可安全表示28位整数)
  const mask = (1 << 28) - 1; // 28位掩码
  const total = (high28 << 28) | low28;
  const shifted = ((total << shiftBits) | (total >>> (56 - shiftBits))) & ((1 << 56) - 1);
  const newHigh28 = (shifted >>> 28) & mask;
  const newLow28 = shifted & mask;
  return [newHigh28, newLow28];
}
// 但注意,上述代码中`total << shiftBits`在JS中可能溢出,更好的做法是使用BigInt或直接操作字节数组。

更稳妥的做法是全程使用 Uint8Array DataView

function permuteWithTable(inputBytes: Uint8Array, table: number[]): Uint8Array {
  // table定义了从输入比特位到输出比特位的映射
  let outputBitLength = table.length;
  let outputByteLength = Math.ceil(outputBitLength / 8);
  let output = new Uint8Array(outputByteLength);
  for (let i = 0; i < outputBitLength; i++) {
    let originalBitPos = table[i] - 1; // 假设table从1开始计数
    let originalByteIndex = Math.floor(originalBitPos / 8);
    let originalBitInByte = 7 - (originalBitPos % 8); // 假设高位在前
    let bitValue = (inputBytes[originalByteIndex] >> originalBitInByte) & 1;
    
    let targetByteIndex = Math.floor(i / 8);
    let targetBitInByte = 7 - (i % 8);
    if (bitValue) {
      output[targetByteIndex] |= (1 << targetBitInByte);
    }
  }
  return output;
}

优势

  • 完全可控,与 dart_des 逻辑可做到逐比特一致。
  • 不依赖特定系统版本。

劣势

  • 性能瓶颈 :JS层循环操作大量比特,性能远低于原生。
  • 安全风险 :自行实现的密码算法容易因侧信道攻击(时间攻击、缓存攻击)而导致密钥泄露,需要极高的编码和安全意识。
  • 实现复杂,容易出错。

4.3 方案三:通过NAPI调用C/C++原生库

这是折中方案,也是性能最优的自行实现方案。我们将DES的核心算法(Feistel轮函数、S盒、P盒等)用C/C++实现,并编译为鸿蒙的Native库( .so 文件)。然后在ArkTS层通过NAPI(Native API)调用这个库。

步骤

  1. 用C语言实现DES算法,确保逻辑与 dart_des 一致。
  2. 编写NAPI接口代码,暴露 encrypt decrypt 等函数给ArkTS。
  3. 在鸿蒙应用的 native 目录下配置编译脚本( CMakeLists.txt )。
  4. 在ArkTS中通过 import native from 'libdes.z.so'; 等方式加载和调用。

优势

  • 性能极致 :C语言实现,效率最高。
  • 安全性可控 :相比JS实现,更易于编写常数时间代码来抵御侧信道攻击。
  • 代码复用 :可以复用已有的、经过验证的C语言DES实现。

劣势

  • 开发复杂度最高,需要熟悉鸿蒙NDK和NAPI开发。
  • 增加包体积和native库的维护成本。
  • 不同CPU架构(arm64-v8a, armeabi-v7a)需要分别编译。

综合建议 :对于金融交易等高性能高安全场景, 优先探索方案一(原生框架) 。若不支持,则 采用方案三(NAPI) 。方案二仅作为原型验证或对性能不敏感的离线场景的备选。

5. 实战适配:基于CryptoFramework的桥接实现

假设我们经过验证,鸿蒙的 cryptoFramework 支持 DES_CBC 模式与 PKCS7 填充。下面我们开始实现桥接层。

5.1 创建HarmonyOS库工程

首先,我们创建一个 Library 类型的HarmonyOS工程,命名为 harmony_des 。这将生成一个HAR包的结构。

harmony_des/
├── entry/
│   └── src/
│       └── main/
│           ├── ets/
│           │   ├── pages/ (可忽略,库工程主入口简单即可)
│           │   └── main_des/ # 我们的核心代码目录
│           │       ├── DES.ets
│           │       ├── TripleDES.ets
│           │       └── index.ets # 导出类
│           ├── resources/
│           └── module.json5
└── oh-package.json5

5.2 实现DES类

main_des/DES.ets 中,我们创建 DES 类。它内部使用 cryptoFramework ,但对外提供类似 dart_des 的接口。

// main_des/DES.ets
import cryptoFramework from '@ohos.security.cryptoFramework';

export class DES {
  private key: cryptoFramework.SymKey;
  private mode: string = 'DES_ECB'; // 默认模式
  private padding: string = 'PKCS7';
  private iv: Uint8Array | null = null; // CBC模式需要IV

  // 构造函数,接受密钥(Uint8Array)和可选参数
  constructor(keyData: Uint8Array, options?: { mode?: string; iv?: Uint8Array; padding?: string }) {
    if (keyData.length !== 8) { // DES密钥64位=8字节
      throw new Error('Invalid DES key length. Must be 8 bytes (64 bits).');
    }
    if (options?.mode) {
      this.mode = options.mode; // 如 'DES_CBC'
    }
    if (options?.iv) {
      if (options.iv.length !== 8) {
        throw new Error('Invalid IV length. Must be 8 bytes for DES.');
      }
      this.iv = options.iv;
    }
    if (options?.padding) {
      this.padding = options.padding;
    }
    this._initKey(keyData).catch((err) => {
      throw new Error(`Failed to init DES key: ${err.message}`);
    });
  }

  private async _initKey(keyData: Uint8Array): Promise<void> {
    try {
      // 1. 创建对称密钥生成器
      let symKeyGenerator = cryptoFramework.createSymKeyGenerator('DES');
      // 2. 将字节数组转换为DataBlob
      let keyBlob: cryptoFramework.DataBlob = { data: keyData };
      // 3. 转换并生成SymKey对象
      this.key = await symKeyGenerator.convertKey(keyBlob);
    } catch (error) {
      console.error(`DES _initKey error: ${JSON.stringify(error)}`);
      throw error;
    }
  }

  // 加密方法
  async encrypt(plainData: Uint8Array): Promise<Uint8Array> {
    return this._cipher(plainData, true);
  }

  // 解密方法
  async decrypt(cipherData: Uint8Array): Promise<Uint8Array> {
    return this._cipher(cipherData, false);
  }

  private async _cipher(input: Uint8Array, isEncrypt: boolean): Promise<Uint8Array> {
    try {
      // 构建算法标识符,例如 'DES_ECB|PKCS7' 或 'DES_CBC|PKCS7'
      let algName = `${this.mode}|${this.padding}`;
      let cipher = cryptoFramework.createCipher(algName);

      // 初始化Cipher
      let modeParams: cryptoFramework.IvParamsSpec | null = null;
      if (this.iv) {
        // 如果是CBC等模式,需要IV参数
        modeParams = cryptoFramework.createIvParamsSpec({ iv: { data: this.iv } });
      }
      await cipher.init(isEncrypt ? cryptoFramework.CryptoMode.ENCRYPT_MODE : cryptoFramework.CryptoMode.DECRYPT_MODE, this.key, modeParams || undefined);

      // 执行加/解密
      let inputBlob: cryptoFramework.DataBlob = { data: input };
      let outputBlob = await cipher.doFinal(inputBlob);

      // 返回Uint8Array结果
      return outputBlob.data;
    } catch (error) {
      console.error(`DES ${isEncrypt ? 'encrypt' : 'decrypt'} error: ${JSON.stringify(error)}`);
      throw new Error(`Cipher operation failed: ${error.message}`);
    }
  }

  // 静态工具方法:生成随机密钥(可用于密钥协商后的本地生成)
  static async generateRandomKey(): Promise<Uint8Array> {
    let symKeyGenerator = cryptoFramework.createSymKeyGenerator('DES');
    let symKey = await symKeyGenerator.generateSymKey();
    let keyBlob = await symKey.getEncoded();
    return keyBlob.data; // 返回8字节的Uint8Array
  }
}

5.3 实现TripleDES类

3DES是DES的加强版,使用两个或三个密钥对数据块进行三次DES加密。在 cryptoFramework 中,它可能是一个独立的算法(如 3DES )。我们的实现需要处理不同的密钥长度(16字节或24字节)。

// main_des/TripleDES.ets
import cryptoFramework from '@ohos.security.cryptoFramework';

export class TripleDES {
  private key: cryptoFramework.SymKey;
  private mode: string = '3DES_ECB';
  private padding: string = 'PKCS7';
  private iv: Uint8Array | null = null;

  constructor(keyData: Uint8Array, options?: { mode?: string; iv?: Uint8Array; padding?: string }) {
    // 3DES密钥长度:16字节(2-key 3DES)或 24字节(3-key 3DES)
    if (keyData.length !== 16 && keyData.length !== 24) {
      throw new Error('Invalid 3DES key length. Must be 16 or 24 bytes.');
    }
    if (options?.mode) {
      this.mode = options.mode; // 如 '3DES_CBC'
    }
    if (options?.iv) {
      if (options.iv.length !== 8) { // 3DES分组长度仍是64位
        throw new Error('Invalid IV length. Must be 8 bytes for 3DES.');
      }
      this.iv = options.iv;
    }
    if (options?.padding) {
      this.padding = options.padding;
    }
    this._initKey(keyData).catch((err) => {
      throw new Error(`Failed to init 3DES key: ${err.message}`);
    });
  }

  private async _initKey(keyData: Uint8Array): Promise<void> {
    try {
      // 注意:算法名可能是'3DES',需要根据系统支持情况调整
      let symKeyGenerator = cryptoFramework.createSymKeyGenerator('3DES');
      let keyBlob: cryptoFramework.DataBlob = { data: keyData };
      this.key = await symKeyGenerator.convertKey(keyBlob);
    } catch (error) {
      // 如果'3DES'不支持,可以尝试'DESede'等别名
      console.error(`Try '3DES' failed, trying 'DESede'...`);
      try {
        let symKeyGenerator = cryptoFramework.createSymKeyGenerator('DESede');
        let keyBlob: cryptoFramework.DataBlob = { data: keyData };
        this.key = await symKeyGenerator.convertKey(keyBlob);
        this.mode = this.mode.replace('3DES', 'DESede'); // 同步更新mode用于后续创建Cipher
      } catch (err2) {
        console.error(`TripleDES _initKey error: ${JSON.stringify(err2)}`);
        throw new Error(`Neither '3DES' nor 'DESede' algorithm is supported.`);
      }
    }
  }

  async encrypt(plainData: Uint8Array): Promise<Uint8Array> {
    return this._cipher(plainData, true);
  }

  async decrypt(cipherData: Uint8Array): Promise<Uint8Array> {
    return this._cipher(cipherData, false);
  }

  private async _cipher(input: Uint8Array, isEncrypt: boolean): Promise<Uint8Array> {
    try {
      let algName = `${this.mode}|${this.padding}`;
      let cipher = cryptoFramework.createCipher(algName);

      let modeParams: cryptoFramework.IvParamsSpec | null = null;
      if (this.iv) {
        modeParams = cryptoFramework.createIvParamsSpec({ iv: { data: this.iv } });
      }
      await cipher.init(isEncrypt ? cryptoFramework.CryptoMode.ENCRYPT_MODE : cryptoFramework.CryptoMode.DECRYPT_MODE, this.key, modeParams || undefined);

      let inputBlob: cryptoFramework.DataBlob = { data: input };
      let outputBlob = await cipher.doFinal(inputBlob);
      return outputBlob.data;
    } catch (error) {
      console.error(`TripleDES ${isEncrypt ? 'encrypt' : 'decrypt'} error: ${JSON.stringify(error)}`);
      throw error;
    }
  }

  static async generateRandomKey(keyLength: 16 | 24 = 24): Promise<Uint8Array> {
    let algName = keyLength === 16 ? '3DES_112' : '3DES_168'; // 根据系统支持调整
    try {
      let symKeyGenerator = cryptoFramework.createSymKeyGenerator('3DES');
      let symKey = await symKeyGenerator.generateSymKey();
      let keyBlob = await symKey.getEncoded();
      if (keyBlob.data.length === keyLength) {
        return keyBlob.data;
      } else {
        // 如果生成的密钥长度不对,进行裁剪或补全(不推荐,此处仅示例)
        throw new Error(`Generated key length mismatch.`);
      }
    } catch (error) {
      console.error(`Generate 3DES key error, fallback to random bytes: ${error}`);
      // 降级方案:使用安全随机数生成器生成字节
      let randomKey = new Uint8Array(keyLength);
      for (let i = 0; i < keyLength; i++) {
        randomKey[i] = Math.floor(Math.random() * 256); // 注意:Math.random()密码学不安全!
      }
      // 在生产环境中,必须使用cryptoFramework.createRandom()生成安全随机数
      return randomKey;
    }
  }
}

5.4 创建索引文件并配置HAR

index.ets 中导出我们的类:

// main_des/index.ets
export { DES } from './DES';
export { TripleDES } from './TripleDES';

修改 oh-package.json5 ,定义HAR包的入口和元信息:

{
  "name": "@ohos/harmony_des",
  "version": "1.0.0",
  "description": "A HarmonyOS adaptation of DES/TripleDES encryption for Flutter dart_des compatibility.",
  "license": "Apache-2.0",
  "keywords": ["encryption", "des", "3des", "harmonyos"],
  "repository": "https://gitee.com/your-repo/harmony_des",
  "homepage": "https://your-site.com",
  "dependencies": {},
  "devDependencies": {},
  "main": "index.ets",
  "types": "index.d.ets"
}

5.5 在业务项目中引用与测试

在另一个HarmonyOS应用工程中,我们可以通过ohpm安装或本地引用这个HAR包。

  1. 本地引用 :在应用的 oh-package.json5 中添加:

    "dependencies": {
      "@ohos/harmony_des": "file:../harmony_des"
    }
    

    然后运行 ohpm install

  2. 编写测试代码

    // 在应用的某个页面ets文件中
    import { DES, TripleDES } from '@ohos/harmony_des';
    
    @Entry
    @Component
    struct TestDesPage {
      @State message: string = 'Hello DES';
    
      aboutToAppear() {
        this.testDes();
      }
    
      async testDes() {
        try {
          // 1. 测试DES ECB模式
          let key = new Uint8Array([0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]);
          let des = new DES(key, { mode: 'DES_ECB', padding: 'PKCS7' });
    
          let plainText = new TextEncoder().encode('Hello HarmonyOS DES!');
          let encrypted = await des.encrypt(plainText);
          console.info(`DES Encrypted (hex): ${this.bytesToHex(encrypted)}`);
    
          let decrypted = await des.decrypt(encrypted);
          let decryptedText = new TextDecoder().decode(decrypted);
          console.info(`DES Decrypted: ${decryptedText}`);
          if (decryptedText === 'Hello HarmonyOS DES!') {
            this.message = 'DES Test PASSED!';
          }
    
          // 2. 测试3DES CBC模式
          let key3des = new Uint8Array(24); // 24字节密钥
          // 填充密钥数据...
          let iv = new Uint8Array(8); // 初始化向量
          // 填充IV数据...
          let tripleDes = new TripleDES(key3des, { mode: '3DES_CBC', iv: iv, padding: 'PKCS7' });
          let encrypted3des = await tripleDes.encrypt(plainText);
          // ... 解密验证
        } catch (error) {
          console.error(`Test failed: ${error.message}`);
          this.message = `Test FAILED: ${error.message}`;
        }
      }
    
      bytesToHex(bytes: Uint8Array): string {
        return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
      }
    
      build() {
        Column() {
          Text(this.message)
            .fontSize(20)
            .margin(20)
        }
        .width('100%')
        .height('100%')
      }
    }
    

6. 适配过程中的关键问题与解决方案

6.1 算法标识符与模式支持问题

鸿蒙 cryptoFramework 支持的算法名称和模式可能因版本而异。常见的名称有 DES DESede (3DES)、 AES 等。模式可能是 ECB CBC CTR 等。填充方式有 PKCS7 PKCS5 NoPadding 等。

问题 :创建 Cipher 时传入 DES_CBC|PKCS7 报错“算法不支持”。

排查与解决

  1. 查询官方文档 :首先查阅对应鸿蒙API版本的 cryptoFramework 文档,确认支持的算法字符串。
  2. 运行时检测 :编写一个检测函数,遍历常见的算法和模式组合,通过 try-catch 来探测支持情况。
    async function detectSupportedAlgorithms() {
      let algorithms = ['DES', 'DES_ECB', 'DES_CBC', 'DESede', '3DES', '3DES_ECB', '3DES_CBC'];
      let modes = ['ECB', 'CBC'];
      let paddings = ['PKCS7', 'PKCS5', 'NoPadding'];
      for (let alg of algorithms) {
        for (let mode of modes) {
          for (let pad of paddings) {
            let algName = `${alg}_${mode}|${pad}`;
            try {
              let cipher = cryptoFramework.createCipher(algName);
              console.info(`Supported: ${algName}`);
            } catch (e) {
              // console.error(`Not supported: ${algName}`);
            }
          }
        }
      }
    }
    
  3. 降级方案 :如果目标系统版本不支持所需的模式(如 CTR ),则需要在桥接层用ArkTS代码模拟该模式。例如, ECB 模式可以直接用 cryptoFramework ,而 CTR 模式可以基于 ECB 和计数器在JS层实现。但这会引入复杂性和性能损耗。

6.2 密钥管理安全强化

在金融场景,密钥不能硬编码在代码中。鸿蒙提供了 @ohos.security.huks (通用密钥库系统)来安全地生成、存储和使用密钥。

最佳实践 :我们的适配库应提供与 Huks 集成的选项。

  1. 生成并存储密钥
    import huks from '@ohos.security.huks';
    
    async function generateAndStoreDesKey(keyAlias: string): Promise<void> {
      let properties: huks.HuksOptions = {
        properties: [
          { tag: huks.HuksTag.HUKS_TAG_ALGORITHM, value: huks.HuksKeyAlg.HUKS_ALG_DES },
          { tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, value: huks.HuksKeySize.HUKS_DES_KEY_SIZE_64 },
          { tag: huks.HuksTag.HUKS_TAG_PURPOSE, value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT },
          { tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, value: huks.HuksCipherMode.HUKS_MODE_CBC },
          { tag: huks.HuksTag.HUKS_TAG_PADDING, value: huks.HuksKeyPadding.HUKS_PADDING_PKCS7 },
          { tag: huks.HuksTag.HUKS_TAG_IV, value: new Uint8Array(8) } // IV可以后续设置
        ]
      };
      let options: huks.HuksOptions = {
        properties: properties
      };
      await huks.generateKey(keyAlias, options);
    }
    
  2. 在DES类中使用Huks密钥 :修改 DES 类的 _initKey 方法,使其支持从 Huks 密钥别名初始化,而不是直接传入密钥字节。这需要调用 huks.initSession huks.updateSession huks.finishSession 来进行加解密操作,流程更复杂,但更安全。

重要提示 :直接使用 cryptoFramework.createCipher 并传入内存中的 SymKey 对象,其密钥材料在内存中仍是可见的。结合 Huks 可以做到密钥不出安全环境,但需要更复杂的会话管理。对于金融级应用,建议深入调研 Huks cryptoFramework 的集成方式。

6.3 数据填充与字节对齐

dart_des 库可能支持多种填充方式,如 PKCS7 ZeroPadding 等。 cryptoFramework 通常支持 PKCS7 (对于分组密码, PKCS7 PKCS5 在8字节分组下等价)。如果原业务代码使用了 ZeroPadding ,我们需要在桥接层手动实现。

解决方案 :在调用原生加密前/后,手动处理填充。

async encryptWithZeroPadding(plainData: Uint8Array): Promise<Uint8Array> {
  // 1. 手动进行ZeroPadding
  let blockSize = 8; // DES分组大小8字节
  let paddingLength = blockSize - (plainData.length % blockSize);
  let paddedData = new Uint8Array(plainData.length + paddingLength);
  paddedData.set(plainData);
  // 不需要填充额外的字节,因为Uint8Array新建时默认就是0

  // 2. 使用NoPadding模式调用原生加密
  let cipher = cryptoFramework.createCipher('DES_ECB|NoPadding');
  // ... 初始化和执行加密
  let encryptedBlob = await cipher.doFinal({data: paddedData});

  // 3. 返回结果(密文长度与填充后明文等长)
  return encryptedBlob.data;
}
// 解密时同理,需要移除末尾的零字节(需注意明文本身末尾可能就是零的情况,这是个缺陷)

6.4 性能测试与优化

迁移后必须进行性能对比测试,确保在鸿蒙上的性能不低于(或可接受于)原Flutter版本。

测试方法

  1. 基准测试 :分别用原 dart_des (在Flutter/Dart VM环境)和适配后的 harmony_des (在鸿蒙真机/模拟器)加密/解密一个1MB的随机数据块,循环100次,计算平均耗时。
  2. 内存分析 :使用鸿蒙 DevEco Studio 的性能分析器,监控加解密过程中的内存分配和GC情况,确保没有内存泄漏或异常峰值。
  3. 优化策略
    • 批量操作 :如果可能,避免对大量小数据包进行频繁的加密调用。可以合并数据或使用流式接口(如果 cryptoFramework 支持)。
    • Worker线程 :加解密是CPU密集型操作,可以放在Worker线程中执行,避免阻塞UI。
    • 缓存Cipher实例 :如果使用相同的密钥和参数进行多次操作,可以复用 Cipher 实例,避免重复初始化开销。

7. 总结与扩展建议

dart_des 适配到鸿蒙,远不止是让代码跑起来。它迫使我们去深入理解算法细节、鸿蒙的安全框架以及不同运行时环境的差异。本次我们选择了基于 cryptoFramework 的桥接方案,这是平衡了开发效率、运行性能和安全性后的推荐路径。

在实际操作中,我还有几点体会:

  1. 测试必须充分 :不仅要测功能正确性(与已知向量对照),还要测边界情况(空数据、极长数据、密钥全0全1等)。建议编写完整的单元测试套件,集成到HAR包的 ohosTest 目录下。
  2. 文档至关重要 :在HAR包的 README.md 中,清晰说明支持的模式、填充方式、系统版本要求、与 dart_des 的API差异以及密钥安全建议。这能极大降低其他开发者的使用成本。
  3. 考虑向后兼容 :如果你的应用需要同时支持鸿蒙和安卓/iOS(通过Flutter),你可能需要维护两套加密代码。一个更好的架构是,在Flutter层定义一个统一的加密接口,然后在不同平台分别实现(鸿蒙用本适配库,安卓/iOS用原 dart_des 或平台原生库)。这可以通过Flutter的 MethodChannel FFI 来实现。
  4. 关注鸿蒙更新 :鸿蒙的 cryptoFramework Huks 仍在快速发展中。关注新版本是否增加了对更多算法(如国密SM4)或模式的支持,以便未来能简化实现或提升性能。

最后,加密无小事。尤其是在金融和硬件协议领域,一个微小的实现偏差或安全疏忽都可能造成严重后果。这套适配方案提供了一个起点,但在将其用于生产环境前,务必结合自身业务的安全要求进行严格评审和渗透测试。

Logo

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

更多推荐