做应用开发久了,你会发现一个残酷的现实:用户根本不在乎你的数据安不安全,但一旦出了事,锅全是你的。密码明文存本地、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 之类非安全用途还是挺好使的。
调用流程就三步:createMd → update → digest。
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 或更大的分段,避免频繁的异步调用开销。
同步版本也有,方法名后面加 Sync:updateSync、digestSync。数据量小、不在主线程的时候用同步版更省事。
三、AES 对称加密:主力加密方案
对称加密是应用层加密的绝对主力。AES 速度快、安全强度高,加密大文件也不在话下。
完整流程:createSymKeyGenerator → generateSymKey → createCipher → init → update → doFinal。
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 加密是三段式操作:initSession → updateSession(可选)→ 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 加密目录。两层独立,缺一不可。
完整流程:
- 用 HUKS 生成 AES 密钥(密钥不出 TEE)
- 用 HUKS 加密明文数据
- 将密文写入 el2 目录
- 读取时先从 el2 目录读密文
- 用 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 上你能做到的极限了。别想着自己造轮子搞什么"更安全"的方案,密码学的东西,用经过验证的标准实现比自己瞎折腾靠谱一万倍。