Flutter适配ArkTS 严格模式下集成 crypto-js:arkts-no-ns-as-obj 与对象字面量限制的破解

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

一、问题背景

在 HarmonyOS 上实现 DES 加密时,由于原生 cryptoFramework 不支持 DES,需要引入 @ohos/crypto-js。但 crypto-js 是 JavaScript 库,在 ArkTS 严格模式下会触发多项编译错误。本文聚焦两个最常见、最易踩坑的限制及解决方案。


二、问题一:arkts-no-ns-as-obj(命名空间不能作为对象)

2.1 错误现象

import { CryptoJS } from '@ohos/crypto-js';

// ❌ 编译错误:Namespaces cannot be used as objects (arkts-no-ns-as-obj)
let crypto = CryptoJS;
let encrypted = crypto.DES.encrypt(data, key, { iv: iv });

错误信息

ARKTS:ERROR File: xxx.ets: Line: X: Col: Y
Namespaces cannot be used as objects (arkts-no-ns-as-obj)

2.2 根本原因

ArkTS 是 TypeScript 的严格子集,禁止将**命名空间(Namespace)**赋给变量。CryptoJS 是一个命名空间,包含 DESenclib 等子对象,直接写 let crypto = CryptoJS 相当于把命名空间当作普通对象使用,违反了 ArkTS 规范。

2.3 正确写法

永远直接通过命名空间调用,不要赋给变量:

// ✅ 正确:直接使用 CryptoJS.xxx
let keyWA = CryptoJS.enc.Utf8.parse(key);
let ivWA = CryptoJS.enc.Utf8.parse(iv);
let encrypted = CryptoJS.DES.encrypt(data, keyWA, { iv: ivWA });
let decrypted = CryptoJS.DES.decrypt(base64Str, keyWA, { iv: ivWA });

2.4 常见误用对照

错误写法 正确写法
let c = CryptoJS; c.DES.encrypt(...) CryptoJS.DES.encrypt(...)
const enc = CryptoJS.enc; enc.Utf8.parse(...) CryptoJS.enc.Utf8.parse(...)
let lib = CryptoJS.lib; lib.WordArray CryptoJS.lib.WordArray

三、问题二:arkts-no-untyped-obj-literals(无类型对象字面量)

3.1 错误现象

// ❌ 错误:Object literal must correspond to some explicitly declared class or interface
let options = { iv: ivWA };
let encrypted = CryptoJS.DES.encrypt(data, keyWA, options);

或解密时:

// ❌ 错误:CipherParams 对象字面量
let cipherParams = {
  ciphertext: wordArray
};
let decrypted = CryptoJS.DES.decrypt(cipherParams, keyWA, { iv: ivWA });

错误信息

ARKTS:ERROR File: xxx.ets
Object literal must correspond to some explicitly declared class or interface

3.2 根本原因

ArkTS 禁止使用无类型的对象字面量作为变量赋值。{ iv: ivWA }{ ciphertext: wordArray } 这类字面量在 ArkTS 中必须对应某个已声明的接口或类,否则无法通过编译。

3.3 加密时的正确写法

作为函数参数直接传递时,{ iv: ivWA } 是可接受的,因为 CryptoJS 的 CipherOption 接口已声明 iv 属性:

// ✅ 正确:作为 encrypt/decrypt 的第三个参数直接传入
let encrypted = CryptoJS.DES.encrypt(data, keyWA, { iv: ivWA });
let decrypted = CryptoJS.DES.decrypt(base64Str, keyWA, { iv: ivWA });

不要先赋给变量再传入:

// ❌ 错误:options 是无类型对象字面量变量
let options = { iv: ivWA };
CryptoJS.DES.encrypt(data, keyWA, options);

3.4 解密时的特殊处理:避免 CipherParams

CryptoJS 的 decrypt 通常接受 CipherParams 对象,其结构为 { ciphertext: WordArray }。在 ArkTS 中创建此类对象字面量会触发限制。

解决方案:使用 Base64 字符串 作为 decrypt 的输入。CryptoJS.DES.decrypt 支持字符串形式的密文:

// ✅ 正确:将密文字节转为 Base64 字符串传入
private doDecryptBytes(data: Uint8Array, key: string, iv: string): string | null {
  let hexStr = this.toHexString(data);
  let wordArray = CryptoJS.enc.Hex.parse(hexStr);
  let base64Str = CryptoJS.enc.Base64.stringify(wordArray);  // 转为 Base64
  let keyWA = CryptoJS.enc.Utf8.parse(key);
  let ivWA = CryptoJS.enc.Utf8.parse(iv);
  // 直接传 base64Str,无需 CipherParams
  let decrypted = CryptoJS.DES.decrypt(base64Str, keyWA, { iv: ivWA });
  return decrypted.toString(CryptoJS.enc.Utf8);
}

四、完整加密解密示例(ArkTS 兼容版)

import { CryptoJS } from '@ohos/crypto-js';

// 加密:直接调用 CryptoJS,参数内联 { iv }
function encrypt(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 wordArrayToBytes(encrypted.ciphertext);
  } catch (e) {
    return null;
  }
}

// 解密:Base64 字符串输入,避免 CipherParams
function decryptFromHex(hexStr: string, key: string, iv: string): string | null {
  try {
    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 "";
  }
}

function 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;
}

五、ArkTS 与 crypto-js 集成速查表

场景 错误写法 正确写法
使用 CryptoJS let c = CryptoJS 直接 CryptoJS.xxx
加密选项 let opt = { iv }; encrypt(..., opt) encrypt(..., { iv }) 内联
解密输入 decrypt({ ciphertext: wa }, ...) decrypt(base64Str, ...) 字符串
编码解析 let enc = CryptoJS.enc; enc.Utf8.parse(...) CryptoJS.enc.Utf8.parse(...)

六、调试技巧

  1. 逐步注释:若不确定哪行触发限制,可逐行注释缩小范围
  2. 查看 crypto-js 类型定义node_modules/@ohos/crypto-js 或 ohpm 安装目录下的 .d.ts 文件,确认 CipherOptionCipherParams 等接口
  3. 优先用字符串 API:CryptoJS 的 encrypt/decrypt 对字符串输入支持良好,可减少对象字面量

七、参考资料

Logo

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

更多推荐