编者按: 密码明文存本地、Token 写进 SharedPreference、身份证号直接 JSON 序列化扔沙箱目录——这些操作在小项目里太常见了。用户根本不在乎你的数据安不安全,但一旦出了事,锅全是你的。HarmonyOS 6.0 提供了一整套从软件加密到硬件级密钥管理的安全体系,但文档分散、API 链路长,很多人看了半天还是不知道怎么落地。开发者@Frame Not Work 把整个链路串起来讲,从最基础的哈希计算一直讲到 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 之类非安全用途还是挺好使的。

调用流程就三步:createMd → update → digest

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,结果不受影响:

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 = Math.min(i + segmentSize, 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 或更大的分段,避免频繁的异步调用开销。

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

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

完整流程:createSymKeyGenerator → generateSymKey → createCipher → init → update → doFinal

AES-128-CBC 模式

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

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

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 会自动处理末尾不满一个分块的情况。

AES-256-GCM 模式(推荐)

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

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 不匹配,解密直接失败,这就实现了完整性校验:

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');
}
AES-128-CBC vs AES-256-GCM 怎么选
维度AES-128-CBCAES-256-GCM
密钥长度128 位256 位
认证能力无,需额外 HMAC内置 AuthTag
IV 长度16 字节12 字节(推荐)
安全等级够用更高,推荐新项目使用

我的建议:新项目一律用 AES-256-GCM。CBC 模式最大的问题是缺乏认证能力,密文被篡改了你都不知道。GCM 自带认证标签,篡改即失败,省心太多。

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

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

RSA 加解密
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。

RSA 签名验证

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

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,你的应用代码也拿不到密钥明文。

HUKS 生成密钥
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),后续所有操作都通过别名引用。密钥本身你永远接触不到。

HUKS 加密

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

async function huksEncryptData(plainText: string): Promise<Uint8Array> {
  let iv = cryptoFramework.createRandom().generateRandomSync(12).data;
  let encryptProps: Array<huks.HuksParam> = [
    // ... 配置属性
    {
      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])
    }
  ];
  let options: huks.HuksOptions = {
    properties: encryptProps,
    inData: new util.TextEncoder().encode(plainText)
  };

  let initResult = await huks.initSession(AES_KEY_ALIAS, options);
  let finishResult = await huks.finishSession(initResult.handle, options);
  return finishResult.outData as Uint8Array;
}
HUKS 解密

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

async function huksDecryptData(
  cipherData: Uint8Array,
  iv: Uint8Array,
  aeadTag: Uint8Array
): Promise<string> {
  let decryptProps: Array<huks.HuksParam> = [
    // ... 配置属性
    {
      tag: huks.HuksTag.HUKS_TAG_NONCE,
      value: iv
    },
    {
      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 提供了三层安全方案,安全性从低到高排列:

Base64 编码(不是加密)
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 只是编码,不是加密。任何人都能解码,没有任何安全性可言。

cryptoFramework 软件加密

适合中等敏感度数据:用户设置项、非关键业务数据、需要跨设备传输的加密数据。密钥在软件层管理,安全性取决于密钥存储方式。

HUKS 硬件级加密

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

七、沙箱隔离与 CE/ECE 加密存储区

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

沙箱目录结构:

  • context.filesDir:应用私有文件目录

  • context.cacheDir:缓存目录

  • context.tempDir:临时文件目录

  • context.preferencesDir:偏好设置目录

  • context.databaseDir:数据库目录

这些目录在 el2 加密分区下(默认),开机后首次解锁才能访问。

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

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

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

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

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

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 参数只在首次创建数据库时生效

  • securityLevel 要和你的数据敏感度匹配

  • 系统默认加密的数据库不支持跨设备打开或卸载重装后打开

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

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

async function secureWriteData(
  context: common.UIAbilityContext,
  fileName: string,
  plainData: string
): Promise<void> {
  // 1. 确保 HUKS 密钥存在
  await initSecureKey();

  // 2. 用 HUKS 加密数据
  let iv = cryptoFramework.createRandom().generateRandomSync(12).data;
  let plainBytes = new util.TextEncoder().encode(plainData);
  // ... HUKS 加密操作,得到 cipherData

  // 3. 将 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);
}

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

十、常见坑与实操建议

正确做法
IV 硬编码每次加密随机生成 IV,和密文一起存储
密钥写在代码里用 HUKS 管理密钥,至少也要用安全的密钥派生方案
Base64 当加密Base64 只用于数据格式转换,不要当作安全手段
encrypt 参数后改建库时就想好要不要加密,首次创建就指定
GCM 解密不传 AuthTag加密时保存 AuthTag,解密时必须传入
HUKS 密钥不判断是否存在先 isKeyItemExist 检查,不存在再创建
el4 目录后台读写后台需要持续访问的数据放 el2,别放 el4
RSA 直接加密大文件大文件用 AES 加密,RSA 只加密 AES 密钥(混合加密)
HUKS session 不 finish三段式操作必须走完:init → update(可选) → finish

十一、写在最后

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

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

📌 文基于开发者 @Frame Not Work 创作的文章整理,感谢开发者的精彩分享。

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

你在 HarmonyOS 安全存储中还遇到过哪些难题?欢迎在评论区留言交流!

Logo

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

更多推荐