HarmonyOS 6.0 文件加密与安全存储:从哈希到硬件级密钥管理全链路实战

做应用开发久了,你会发现一个残酷的现实:用户根本不在乎你的数据安不安全,但一旦出了事,锅全是你的。密码明文存本地、Token 写进 SharedPreference、身份证号直接 JSON 序列化扔沙箱目录------这些操作在小作坊项目里太常见了。HarmonyOS 6.0提供了一整套从软件加密到硬件级密钥管理的安全体系,但文档分散、API 链路长,很多人看了半天还是不知道怎么落地。这篇文章把整个链路串起来讲,从最基础的哈希计算一直讲到 HUKS 硬件级密钥管理,配合实际可跑的 ArkTS 代码,看完你就能直接用到项目里。

一、cryptoFramework 模块总览

HarmonyOS 6.0的加解密能力主要由 @kit.CryptoArchitectureKit 提供,这套 API 覆盖了三大领域:

  • 哈希(消息摘要):SHA-256、SHA-384、SHA-512、MD5 等,用于数据完整性校验和指纹生成
  • 对称加密:AES-128/192/256,支持 CBC、GCM、ECB、CTR 等模式,适合大数据量加解密
  • 非对称加密:RSA、ECC、SM2 等,用于密钥协商、数字签名、小数据加密

这套 API 的设计模式非常统一:创建实例 → 初始化 → 更新数据 → 获取结果。不管你用哪种算法,流程都是这个套路,上手成本不高。

另外还有一套 @kit.UniversalKeystoreKit(HUKS),专门做密钥管理,密钥全程不离开 TEE 可信执行环境,安全性比 cryptoFramework 高一个级别。后面会详细讲。

二、哈希计算:数据指纹的第一道关

哈希不是加密,但它是安全存储的基础设施。文件完整性校验、密码存储(配合盐值)、数据去重,都离不开哈希。

HarmonyOS 支持的哈希算法:SHA-256(32 字节)、SHA-384(48 字节)、SHA-512(64 字节)、MD5(16 字节)。MD5 已经不推荐用于安全场景了,但做文件去重、缓存 key 之类非安全用途还是挺好使的。

调用流程就三步:createMdupdatedigest

typescript 复制代码
import { cryptoFramework } from '@kit.CryptoArchitectureKit';
import { buffer } from '@kit.ArkTS';

async function computeSha256(input: string): Promise<string> {
  let md = cryptoFramework.createMd('SHA256');
  let inputBytes: cryptoFramework.DataBlob = {
    data: new Uint8Array(buffer.from(input, 'utf-8').buffer)
  };
  await md.update(inputBytes);
  let result = await md.digest();
  let hexStr = '';
  for (let i = 0; i < result.data.length; i++) {
    let hex = result.data[i].toString(16).padStart(2, '0');
    hexStr += hex;
  }
  return hexStr;
}

数据量大的场景可以分段 update,结果不受影响:

typescript 复制代码
async function computeSha256BySegment(longText: string): Promise<string> {
  let md = cryptoFramework.createMd('SHA256');
  let bytes = new Uint8Array(buffer.from(longText, 'utf-8').buffer);
  let segmentSize = 4096;
  for (let i = 0; i < bytes.length; i += segmentSize) {
    let end = i + segmentSize;
    if (end > bytes.length) {
      end = bytes.length;
    }
    let segment: cryptoFramework.DataBlob = {
      data: bytes.subarray(i, end)
    };
    await md.update(segment);
  }
  let result = await md.digest();
  let hexStr = '';
  for (let i = 0; i < result.data.length; i++) {
    hexStr += result.data[i].toString(16).padStart(2, '0');
  }
  return hexStr;
}

这里有个细节要注意:update 接口对单次传入的数据量没有限制,分段只是为了控制内存占用。对于文件哈希计算,建议用 4KB 或更大的分段,避免频繁的异步调用开销。

同步版本也有,方法名后面加 SyncupdateSyncdigestSync。数据量小、不在主线程的时候用同步版更省事。

三、AES 对称加密:主力加密方案

对称加密是应用层加密的绝对主力。AES 速度快、安全强度高,加密大文件也不在话下。

完整流程:createSymKeyGeneratorgenerateSymKeycreateCipherinitupdatedoFinal

3.1 AES-128-CBC 模式

CBC 是最经典的分组模式,需要 IV(初始化向量)参与运算:

typescript 复制代码
import { cryptoFramework } from '@kit.CryptoArchitectureKit';
import { buffer } from '@kit.ArkTS';

async function aesCbcEncrypt(plainText: string): Promise<cryptoFramework.DataBlob> {
  let keyGenerator = cryptoFramework.createSymKeyGenerator('AES128');
  let symKey = await keyGenerator.generateSymKey();

  let ivBytes = cryptoFramework.createRandom().generateRandomSync(16);
  let ivParamsSpec: cryptoFramework.IvParamsSpec = {
    algName: 'IvParamsSpec',
    iv: { data: ivBytes.data }
  };

  let cipher = cryptoFramework.createCipher('AES128|CBC|PKCS7');
  await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, symKey, ivParamsSpec);

  let input: cryptoFramework.DataBlob = {
    data: new Uint8Array(buffer.from(plainText, 'utf-8').buffer)
  };
  let encryptResult = await cipher.doFinal(input);
  return encryptResult;
}

解密时用同一个 key 和 IV,模式换成 DECRYPT_MODE

typescript 复制代码
async function aesCbcDecrypt(
  symKey: cryptoFramework.SymKey,
  cipherData: cryptoFramework.DataBlob,
  ivData: Uint8Array
): Promise<string> {
  let ivParamsSpec: cryptoFramework.IvParamsSpec = {
    algName: 'IvParamsSpec',
    iv: { data: ivData }
  };

  let decoder = cryptoFramework.createCipher('AES128|CBC|PKCS7');
  await decoder.init(cryptoFramework.CryptoMode.DECRYPT_MODE, symKey, ivParamsSpec);
  let decryptResult = await decoder.doFinal(cipherData);

  let output = buffer.from(decryptResult.data).toString('utf-8');
  return output;
}

CBC 模式有几个坑要注意:IV 必须随机生成,不能硬编码;IV 需要和密文一起存储,解密时要用;PKCS7 填充模式下 doFinal 会自动处理末尾不满一个分块的情况。

3.2 AES-256-GCM 模式

GCM 是我更推荐的模式。它不仅能加密,还带认证标签(AuthTag),能同时保证数据的机密性和完整性。CBC 模式只能加密,如果你需要验证数据有没有被篡改,还得自己算 HMAC,而 GCM 一步到位。

typescript 复制代码
function buildGcmParamsSpec(): cryptoFramework.GcmParamsSpec {
  let ivBytes = cryptoFramework.createRandom().generateRandomSync(12);
  let aadBytes = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
  let tagBytes = new Uint8Array(16);

  let gcmParams: cryptoFramework.GcmParamsSpec = {
    algName: 'GcmParamsSpec',
    iv: { data: ivBytes.data },
    aad: { data: aadBytes },
    authTag: { data: tagBytes }
  };
  return gcmParams;
}

async function aesGcmEncrypt(
  symKey: cryptoFramework.SymKey,
  plainText: string
): Promise<cryptoFramework.DataBlob> {
  let gcmParams = buildGcmParamsSpec();

  let cipher = cryptoFramework.createCipher('AES128|GCM|PKCS7');
  await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, symKey, gcmParams);

  let input: cryptoFramework.DataBlob = {
    data: new Uint8Array(buffer.from(plainText, 'utf-8').buffer)
  };
  let encryptResult = await cipher.doFinal(input);

  // GCM 模式下 doFinal 返回密文,authTag 需要从 gcmParams.authTag 中读取
  // 解密时必须使用加密阶段生成的 authTag
  return encryptResult;
}

解密时需要把加密阶段生成的 authTag 放进 GcmParamsSpec 传给 init,如果 authTag 不匹配,解密直接失败,这就实现了完整性校验:

typescript 复制代码
async function aesGcmDecrypt(
  symKey: cryptoFramework.SymKey,
  cipherData: cryptoFramework.DataBlob,
  gcmParams: cryptoFramework.GcmParamsSpec
): Promise<string> {
  let decoder = cryptoFramework.createCipher('AES128|GCM|PKCS7');
  await decoder.init(cryptoFramework.CryptoMode.DECRYPT_MODE, symKey, gcmParams);
  let decryptResult = await decoder.doFinal(cipherData);

  return buffer.from(decryptResult.data).toString('utf-8');
}

3.3 AES-128-CBC vs AES-256-GCM 怎么选

维度 AES-128-CBC AES-256-GCM
密钥长度 128 位 256 位(密钥生成器指定 AES256)
认证能力 无,需额外 HMAC 内置 AuthTag
IV 长度 16 字节 12 字节(推荐)
填充模式 PKCS7 NoPadding 或 PKCS7
性能 略慢,但省了 HMAC 计算
安全等级 够用 更高,推荐新项目使用

我的建议:新项目一律用 AES-256-GCM。CBC 模式最大的问题是缺乏认证能力,密文被篡改了你都不知道,解密出来一坨乱码还以为是自己写错了。GCM 自带认证标签,篡改即失败,省心太多。128 位密钥在当前算力下确实够安全,但 256 位是趋势,性能差距可以忽略。

四、RSA 非对称加密:公钥加密、私钥解密

RSA 的典型场景不是直接加密业务数据------它太慢了,而且有长度限制(1024 位密钥最多加密 117 字节,2048 位最多 245 字节)。RSA 真正的价值在于:密钥协商、数字签名、加密小数据(比如 AES 密钥)。

4.1 RSA 加解密

typescript 复制代码
import { cryptoFramework } from '@kit.CryptoArchitectureKit';
import { buffer } from '@kit.ArkTS';

async function rsaEncryptDemo(): Promise<void> {
  // 生成 RSA 2048 密钥对
  let keyGenerator = cryptoFramework.createAsyKeyGenerator('RSA2048');
  let keyPair = await keyGenerator.generateKeyPair();

  let message = 'SensitiveData123';
  let input: cryptoFramework.DataBlob = {
    data: new Uint8Array(buffer.from(message, 'utf-8').buffer)
  };

  // 公钥加密
  let cipher = cryptoFramework.createCipher('RSA2048|PKCS1');
  await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, keyPair.pubKey, null);
  let encryptResult = await cipher.doFinal(input);

  // 私钥解密(必须创建新的 Cipher 实例)
  let decoder = cryptoFramework.createCipher('RSA2048|PKCS1');
  await decoder.init(cryptoFramework.CryptoMode.DECRYPT_MODE, keyPair.priKey, null);
  let decryptResult = await decoder.doFinal(encryptResult);

  let decrypted = buffer.from(decryptResult.data).toString('utf-8');
  console.info('Decrypted: ' + decrypted);
}

注意两点:一是 RSA 的 Cipher 实例不支持重复 init,每次加解密都要 new 一个;二是非对称加密的 params 参数传 null 就行,不像 AES 那样要传 IvParamsSpec 或 GcmParamsSpec。

4.2 RSA 签名验证

签名是 RSA 另一个核心用途------用私钥签名,用公钥验证,证明数据确实来自持有私钥的一方:

typescript 复制代码
async function rsaSignVerifyDemo(): Promise<void> {
  let keyGenerator = cryptoFramework.createAsyKeyGenerator('RSA2048');
  let keyPair = await keyGenerator.generateKeyPair();

  let message = 'Contract content here';
  let input: cryptoFramework.DataBlob = {
    data: new Uint8Array(buffer.from(message, 'utf-8').buffer)
  };

  // 私钥签名
  let signer = cryptoFramework.createSign('RSA2048|PKCS1|SHA256');
  await signer.init(keyPair.priKey);
  let signResult = await signer.sign(input);

  // 公钥验签
  let verifier = cryptoFramework.createVerify('RSA2048|PKCS1|SHA256');
  await verifier.init(keyPair.pubKey);
  let isValid = await verifier.verify(input, signResult);

  console.info('Signature valid: ' + isValid);
}

签名和验签的算法字符串必须一致,RSA2048|PKCS1|SHA256 里的每一项都得对上。另外 RSA 密钥长度建议至少 2048 位,1024 位在当前算力下已经不安全了。

五、HUKS 密钥管理:硬件级安全的天花板

cryptoFramework 做加解密没问题,但密钥的管理是个软肋。你在软件层生成的 AES 密钥,最终还是存在内存里,root 设备或者内存 dump 理论上能拿到。HUKS(Universal Keystore Kit)解决的就是这个问题------密钥生成、存储、使用全在 TEE(可信执行环境)里完成,密钥永远不出 TEE,你的应用代码也拿不到密钥明文。

5.1 HUKS 生成密钥

typescript 复制代码
import { huks } from '@kit.UniversalKeystoreKit';

const AES_KEY_ALIAS = 'my_app_aes_key';

function getAesGenerateProperties(): Array<huks.HuksParam> {
  return [
    {
      tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
      value: huks.HuksKeyAlg.HUKS_ALG_AES
    },
    {
      tag: huks.HuksTag.HUKS_TAG_KEY_SIZE,
      value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256
    },
    {
      tag: huks.HuksTag.HUKS_TAG_PURPOSE,
      value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT |
             huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT
    },
    {
      tag: huks.HuksTag.HUKS_TAG_PADDING,
      value: huks.HuksKeyPadding.HUKS_PADDING_NONE
    },
    {
      tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE,
      value: huks.HuksCipherMode.HUKS_MODE_GCM
    }
  ];
}

async function generateHuksAesKey(): Promise<void> {
  let properties = getAesGenerateProperties();
  let options: huks.HuksOptions = {
    properties: properties
  };
  await huks.generateKeyItem(AES_KEY_ALIAS, options);
  console.info('HUKS AES key generated');
}

注意看,这里没有 generateSymKey 返回密钥对象的步骤。HUKS 的密钥由系统管理,你拿到的是一个别名(alias),后续所有操作都通过别名引用。密钥本身你永远接触不到。

5.2 HUKS 加密

HUKS 加密是三段式操作:initSessionupdateSession(可选)→ finishSession

typescript 复制代码
let huksHandle: number = 0;

function getAesEncryptProperties(iv: Uint8Array): Array<huks.HuksParam> {
  return [
    {
      tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
      value: huks.HuksKeyAlg.HUKS_ALG_AES
    },
    {
      tag: huks.HuksTag.HUKS_TAG_KEY_SIZE,
      value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256
    },
    {
      tag: huks.HuksTag.HUKS_TAG_PURPOSE,
      value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT
    },
    {
      tag: huks.HuksTag.HUKS_TAG_PADDING,
      value: huks.HuksKeyPadding.HUKS_PADDING_NONE
    },
    {
      tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE,
      value: huks.HuksCipherMode.HUKS_MODE_GCM
    },
    {
      tag: huks.HuksTag.HUKS_TAG_NONCE,
      value: iv
    },
    {
      tag: huks.HuksTag.HUKS_TAG_ASSOCIATED_DATA,
      value: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
    }
  ];
}

async function huksEncryptData(plainText: string): Promise<Uint8Array> {
  let iv = cryptoFramework.createRandom().generateRandomSync(12).data;
  let encryptProps = getAesEncryptProperties(iv);
  let options: huks.HuksOptions = {
    properties: encryptProps,
    inData: new util.TextEncoder().encode(plainText)
  };

  let initResult = await huks.initSession(AES_KEY_ALIAS, options);
  huksHandle = initResult.handle;

  let finishResult = await huks.finishSession(huksHandle, options);
  return finishResult.outData as Uint8Array;
}

5.3 HUKS 解密

解密流程和加密一模一样,只是 PURPOSE 换成 DECRYPT,并且 GCM 模式下需要传入 AEAD 标签:

typescript 复制代码
async function huksDecryptData(
  cipherData: Uint8Array,
  iv: Uint8Array,
  aeadTag: Uint8Array
): Promise<string> {
  let decryptProps: Array<huks.HuksParam> = [
    {
      tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
      value: huks.HuksKeyAlg.HUKS_ALG_AES
    },
    {
      tag: huks.HuksTag.HUKS_TAG_KEY_SIZE,
      value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256
    },
    {
      tag: huks.HuksTag.HUKS_TAG_PURPOSE,
      value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT
    },
    {
      tag: huks.HuksTag.HUKS_TAG_PADDING,
      value: huks.HuksKeyPadding.HUKS_PADDING_NONE
    },
    {
      tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE,
      value: huks.HuksCipherMode.HUKS_MODE_GCM
    },
    {
      tag: huks.HuksTag.HUKS_TAG_NONCE,
      value: iv
    },
    {
      tag: huks.HuksTag.HUKS_TAG_ASSOCIATED_DATA,
      value: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
    },
    {
      tag: huks.HuksTag.HUKS_TAG_AE_TAG,
      value: aeadTag
    }
  ];
  let options: huks.HuksOptions = {
    properties: decryptProps,
    inData: cipherData
  };

  let initResult = await huks.initSession(AES_KEY_ALIAS, options);
  let finishResult = await huks.finishSession(initResult.handle, options);
  let plainBytes = finishResult.outData as Uint8Array;
  return new util.TextDecoder().decodeToString(plainBytes);
}

HUKS 的核心价值就在于:加密解密操作在 TEE 内完成,密钥明文永远不会出现在普通执行环境(REE)的内存中。即使攻击者拿到了设备的 root 权限,也无法提取 HUKS 管理的密钥。这是软件层加密做不到的。

六、安全存储策略选择

HarmonyOS 6.0 提供了三层安全方案,安全性从低到高排列:

6.1 Base64 编码(不是加密)

typescript 复制代码
import { util } from '@kit.ArkTS';

function base64Encode(input: string): string {
  let encoder = new util.Base64Helper();
  let bytes = new util.TextEncoder().encode(input);
  return encoder.encodeToString(bytes);
}

Base64 只是编码,不是加密。任何人都能解码,没有任何安全性可言。唯一合理的用途是在文本协议中传输二进制数据。如果你的"加密"方案就是把密码 Base64 一下存本地,建议直接写明文算了,至少你不骗自己。

6.2 cryptoFramework 软件加密

适合中等敏感度数据:用户设置项、非关键业务数据、需要跨设备传输的加密数据。密钥在软件层管理,安全性取决于密钥存储方式。如果你把密钥硬编码在代码里,那和没加密差不多。

6.3 HUKS 硬件级加密

适合高敏感数据:密码、Token、身份证号、金融信息、健康数据。密钥由 TEE 管理,不可提取。这是目前 HarmonyOS 上你能拿到的最高安全等级。

选型建议:看数据敏感度。S1/S2 级别用 cryptoFramework 足够,S3/S4 级别必须上 HUKS。如果你不确定数据该归哪个级别,那就默认当高级别处理------过度加密的性能损耗微乎其微,加密不足的后果你承担不起。

七、沙箱隔离:系统给你的第一道防线

HarmonyOS 的应用沙箱机制是安全存储的基础。每个应用有自己独立的沙箱目录,应用 A 默认无法访问应用 B 的文件。这个隔离是系统强制的,不需要你做任何额外工作。

沙箱目录结构:

  • context.filesDir:应用私有文件目录
  • context.cacheDir:缓存目录
  • context.tempDir:临时文件目录
  • context.preferencesDir:偏好设置目录
  • context.databaseDir:数据库目录

这些目录在 el2 加密分区下(默认),开机后首次解锁才能访问。但沙箱隔离不是万能的------root 设备可以绕过,跨设备同步可能泄露,备份导出也会把数据带出去。所以敏感数据还是要加密,沙箱只是第一道防线。

八、CE/ECE 加密存储区:分级加密目录

HarmonyOS 按加密强度把沙箱目录分成了四个等级:

等级 说明 适用场景
el1 设备级加密,开机即可访问 闹钟、壁纸、通知
el2 用户级加密,首次解锁后可访问 默认档位,大多数应用数据
el3 文件关闭后锁屏,再次打开需重新解锁 即时通讯消息、邮件
el4 锁屏 10 秒后密钥丢弃,重新解锁才能访问 金融应用、密码管理器

使用 el2 以上目录,只需通过 context 获取对应路径:

typescript 复制代码
import { common } from '@kit.AbilityKit';

function getSecureDir(context: common.UIAbilityContext): string {
  // el2 目录:用户首次解锁后可访问
  return context.filesDir;
}

关键点:el2 是默认档位,绝大多数数据放这里就够了。el3/el4 需要更高的安全等级,访问限制也更严格------锁屏后文件可能直接不可读。如果你的应用在后台需要持续访问文件,别用 el4,否则锁屏后读写会失败。

九、RDB 加密数据库:结构化数据的安全存储

如果你的敏感数据是结构化的(比如用户信息表、交易记录表),用文件加密存储解析起来太麻烦,直接用加密的 RDB 数据库是更好的选择。

创建加密数据库只需要在 StoreConfig 里设置 encrypt: true

typescript 复制代码
import { relationalStore } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';

async function createEncryptedDb(context: common.UIAbilityContext): Promise<relationalStore.RdbStore> {
  const STORE_CONFIG: relationalStore.StoreConfig = {
    name: 'SecureApp.db',
    securityLevel: relationalStore.SecurityLevel.S3,
    encrypt: true
  };

  let store = await relationalStore.getRdbStore(context, STORE_CONFIG);

  // 建表
  const CREATE_TABLE_SQL = 'CREATE TABLE IF NOT EXISTS user_credentials ' +
    '(id INTEGER PRIMARY KEY AUTOINCREMENT, ' +
    'username TEXT NOT NULL, ' +
    'encrypted_password TEXT NOT NULL, ' +
    'salt TEXT NOT NULL)';

  await store.executeSql(CREATE_TABLE_SQL);
  return store;
}

几个重要细节:

第一,encrypt 参数只在首次创建数据库时生效。如果数据库已经创建过了,你后面改 encrypt 设置是没用的。所以一开始就要想清楚要不要加密。

第二,securityLevel 要和你的数据敏感度匹配。S3 适合大多数应用,S4 适合金融/健康类应用。

第三,系统默认加密的数据库不支持跨设备打开或卸载重装后打开。如果你需要导出数据,得先把加密数据库迁移成非加密的或者自定义密钥的。

第四,从 API 22 开始支持 rekeyEx 接口调整加密属性,但低版本不支持,别想当然。

键值型数据库(KV Store)也支持加密,同样是创建时指定 encrypt: true

typescript 复制代码
import { distributedKVStore } from '@kit.ArkData';

const KV_OPTIONS: distributedKVStore.Options = {
  createIfMissing: true,
  encrypt: true,
  securityLevel: distributedKVStore.SecurityLevel.S3
};

十、实战:HUKS + el2 二次加密方案

对于最高敏感度的数据(S4 级别),官方推荐的做法是二次加密:先用 HUKS 在 TEE 内加密数据,再把密文写入 el2 加密目录。两层独立,缺一不可。

完整流程:

  1. 用 HUKS 生成 AES 密钥(密钥不出 TEE)
  2. 用 HUKS 加密明文数据
  3. 将密文写入 el2 目录
  4. 读取时先从 el2 目录读密文
  5. 用 HUKS 解密密文
typescript 复制代码
import { huks } from '@kit.UniversalKeystoreKit';
import { fileIo } from '@kit.CoreFileKit';
import { util } from '@kit.ArkTS';
import { common } from '@kit.AbilityKit';

const SECURE_KEY_ALIAS = 'app_secure_data_key';

async function initSecureKey(): Promise<void> {
  let isKeyExist = await huks.isKeyItemExist(SECURE_KEY_ALIAS, { properties: [] });
  if (!isKeyExist) {
    let properties: Array<huks.HuksParam> = [
      {
        tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
        value: huks.HuksKeyAlg.HUKS_ALG_AES
      },
      {
        tag: huks.HuksTag.HUKS_TAG_KEY_SIZE,
        value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256
      },
      {
        tag: huks.HuksTag.HUKS_TAG_PURPOSE,
        value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT |
               huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT
      },
      {
        tag: huks.HuksTag.HUKS_TAG_PADDING,
        value: huks.HuksKeyPadding.HUKS_PADDING_NONE
      },
      {
        tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE,
        value: huks.HuksCipherMode.HUKS_MODE_GCM
      }
    ];
    await huks.generateKeyItem(SECURE_KEY_ALIAS, { properties: properties });
  }
}

async function secureWriteData(
  context: common.UIAbilityContext,
  fileName: string,
  plainData: string
): Promise<void> {
  await initSecureKey();

  let iv = cryptoFramework.createRandom().generateRandomSync(12).data;
  let aad = new Uint8Array([0x01, 0x02, 0x03, 0x04]);
  let plainBytes = new util.TextEncoder().encode(plainData);

  let encryptProps: Array<huks.HuksParam> = [
    {
      tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
      value: huks.HuksKeyAlg.HUKS_ALG_AES
    },
    {
      tag: huks.HuksTag.HUKS_TAG_KEY_SIZE,
      value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256
    },
    {
      tag: huks.HuksTag.HUKS_TAG_PURPOSE,
      value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT
    },
    {
      tag: huks.HuksTag.HUKS_TAG_PADDING,
      value: huks.HuksKeyPadding.HUKS_PADDING_NONE
    },
    {
      tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE,
      value: huks.HuksCipherMode.HUKS_MODE_GCM
    },
    {
      tag: huks.HuksTag.HUKS_TAG_NONCE,
      value: iv
    },
    {
      tag: huks.HuksTag.HUKS_TAG_ASSOCIATED_DATA,
      value: aad
    }
  ];
  let options: huks.HuksOptions = {
    properties: encryptProps,
    inData: plainBytes
  };

  let initResult = await huks.initSession(SECURE_KEY_ALIAS, options);
  let finishResult = await huks.finishSession(initResult.handle, options);
  let cipherData = finishResult.outData as Uint8Array;

  // 将 IV + 密文拼接后写入 el2 目录
  let fileData = new Uint8Array(iv.length + cipherData.length);
  fileData.set(iv, 0);
  fileData.set(cipherData, iv.length);

  let filePath = context.filesDir + '/' + fileName;
  let file = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
  fileIo.writeSync(file.fd, fileData.buffer);
  fileIo.closeSync(file.fd);
}

async function secureReadData(
  context: common.UIAbilityContext,
  fileName: string
): Promise<string> {
  let filePath = context.filesDir + '/' + fileName;
  let file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
  let stat = fileIo.statSync(filePath);
  let buf = new ArrayBuffer(stat.size);
  fileIo.readSync(file.fd, buf);
  fileIo.closeSync(file.fd);

  let allData = new Uint8Array(buf);
  let iv = allData.subarray(0, 12);
  let cipherData = allData.subarray(12);
  let aad = new Uint8Array([0x01, 0x02, 0x03, 0x04]);

  // GCM 模式:密文末尾 16 字节是 AuthTag
  let tagSize = 16;
  let actualCipher = cipherData.subarray(0, cipherData.length - tagSize);
  let authTag = cipherData.subarray(cipherData.length - tagSize);

  let decryptProps: Array<huks.HuksParam> = [
    {
      tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
      value: huks.HuksKeyAlg.HUKS_ALG_AES
    },
    {
      tag: huks.HuksTag.HUKS_TAG_KEY_SIZE,
      value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256
    },
    {
      tag: huks.HuksTag.HUKS_TAG_PURPOSE,
      value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT
    },
    {
      tag: huks.HuksTag.HUKS_TAG_PADDING,
      value: huks.HuksKeyPadding.HUKS_PADDING_NONE
    },
    {
      tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE,
      value: huks.HuksCipherMode.HUKS_MODE_GCM
    },
    {
      tag: huks.HuksTag.HUKS_TAG_NONCE,
      value: iv
    },
    {
      tag: huks.HuksTag.HUKS_TAG_ASSOCIATED_DATA,
      value: aad
    },
    {
      tag: huks.HuksTag.HUKS_TAG_AE_TAG,
      value: authTag
    }
  ];
  let options: huks.HuksOptions = {
    properties: decryptProps,
    inData: actualCipher
  };

  let initResult = await huks.initSession(SECURE_KEY_ALIAS, options);
  let finishResult = await huks.finishSession(initResult.handle, options);
  let plainBytes = finishResult.outData as Uint8Array;
  return new util.TextDecoder().decodeToString(plainBytes);
}

这段代码做了什么?明文数据经过 HUKS(在 TEE 内)用 AES-256-GCM 加密,IV 和密文拼接后写入 el2 目录。攻击者就算拿到了文件,面对的是两层加密:HUKS 的 AES-256-GCM 和 el2 的磁盘级加密。密钥在 TEE 里,文件在加密分区里,两把锁缺一把都打不开。

十一、常见坑与实操建议

说明 正确做法
IV 硬编码 每次加密用同一个 IV,CBC 模式下相同明文产生相同密文,攻击者可以识别数据模式 每次加密随机生成 IV,和密文一起存储
密钥写在代码里 字符串硬编码、常量定义,反编译直接暴露 用 HUKS 管理密钥,至少也要用安全的密钥派生方案
Base64 当加密 编码不是加密,任何人都能解码 Base64 只用于数据格式转换,不要当作安全手段
encrypt 参数后改 RDB 数据库创建后再改 encrypt 不会生效 建库时就想好要不要加密,首次创建就指定
GCM 解密不传 AuthTag AuthTag 是 GCM 完整性校验的关键,不传就失去了认证能力 加密时保存 AuthTag,解密时必须传入
HUKS 密钥不判断是否存在 重复 generateKeyItem 同名密钥会报错 先 isKeyItemExist 检查,不存在再创建
el4 目录后台读写 锁屏后 el4 密钥丢弃,后台 Service 读写会失败 后台需要持续访问的数据放 el2,别放 el4
RSA 直接加密大文件 RSA 有长度限制,2048 位密钥最多加密 245 字节 大文件用 AES 加密,RSA 只加密 AES 密钥(混合加密)
密文存 Preferences Preferences 不适合存大二进制数据,Base64 后体积膨胀 33% 敏感数据用文件存储 + 加密,或用加密 RDB
HUKS session 不 finish initSession 后不调 finishSession 会导致会话泄漏 三段式操作必须走完:init → update(可选) → finish
CBC 模式不验证完整性 密文被篡改后解密出乱码但不报错,可能被利用 CBC 模式配合 HMAC 使用,或直接用 GCM
RSA 密钥长度不够 1024 位 RSA 已不安全,暴力破解成本持续下降 新项目至少 RSA 2048,有条件用 3072 或 4096

安全存储不是一道选择题,而是一道必答题。HarmonyOS 6.0给了你从软件加密到硬件级密钥管理的完整工具链,cryptoFramework 解决日常加密需求,HUKS 兜底高敏感数据,el2/el4 分级目录做系统层防护,RDB 加密数据库处理结构化数据。工具都在这了,用不用、怎么用,就看你对自己用户数据的态度了。

最后说一句大实话:安全方案没有绝对的安全,只有成本和收益的权衡。HUKS + el2 的二次加密方案已经是目前 HarmonyOS 上你能做到的极限了。别想着自己造轮子搞什么"更安全"的方案,密码学的东西,用经过验证的标准实现比自己瞎折腾靠谱一万倍。

相关推荐
Georgewu2 小时前
【HarmonyOS AI】鸿蒙开发者需要搞懂的 AI Coding 概念
harmonyos
FF2501_940228582 小时前
HarmonyOS开发实战:小分享-App项目架构全景解析
后端·华为·harmonyos·鸿蒙
解局易否结局3 小时前
鸿蒙原生开发实战:Native 图片处理与二维码全链路解析
华为·harmonyos
栩栩云生3 小时前
命令行的门槛从"会写"变成了"会拦"
安全·ai编程·命令行
GitLqr3 小时前
别再盲目复制了:彻底搞懂 CORS 的本质与那些“神坑”
安全·http·面试
xian_wwq4 小时前
【案例分析】Hugging Face生产基础设施入侵攻击分析
网络·安全
<小智>4 小时前
鸿蒙多功能工具箱开发实战(十二)-二十四节气与黄历数据展示
ui·华为·harmonyos
Georgewu4 小时前
【HarmonyOS AI】DevEco CLI、Skills、知识库运用AI Coding提效详解
harmonyos
2501_919749035 小时前
华为鸿蒙免费听歌APP+免费铃声APP
华为·harmonyos