鸿蒙操作系统使用 cryptoFramework 实现 AES-GCM 加解密实战
一、背景说明
HarmonyOS 开发者官方今日持续直播讲解应用安全主题,数据落盘与传输的机密性保护是其中绕不开的一环。本文不展开安全体系,聚焦一个具体技术点:使用 Crypto Architecture Kit(@kit.CryptoArchitectureKit)的 cryptoFramework,在鸿蒙应用内实现 AES-128-GCM 加解密的完整闭环——密钥生成、GCM 参数构造、加密、认证标签提取、解密与篡改校验,全部代码可直接编译运行。
二、技术原理
cryptoFramework 是鸿蒙统一的加解密算法框架,屏蔽底层硬件差异(支持软算法与硬件加速),核心概念如下:
- SymKeyGenerator:对称密钥生成器,
createSymKeyGenerator('AES128')指定算法与长度,generateSymKey()随机生成,convertKey()从已有字节恢复。 - Cipher:加解密实例,
createCipher('AES128|GCM')用字符串声明算法/分组模式(分段场景可加|PKCS7填充)。 - GcmParamsSpec:GCM 模式(AEAD 认证加密)的三要素——
iv(12 字节随机初始化向量)、aad(附加认证数据,可不参与加密但参与完整性校验)、authTag(16 字节认证标签)。 - CryptoMode:
ENCRYPT_MODE/DECRYPT_MODE,通过cipher.init()指定。
GCM 模式的关键机制:加密时 update 输出密文、doFinal(null) 输出 16 字节 authTag;解密时把 authTag 填回 GcmParamsSpec 传入 init,若密文或 AAD 被篡改,doFinal 校验失败抛出异常(错误码 17630001)。数据流:
createSymKeyGenerator → generateSymKey → createCipher('AES128|GCM') → init(模式, key, GcmParamsSpec) → update(明文) → doFinal(null)=authTag → [密文+tag+iv 一起存储/传输] → 解密端 init 时回填 tag → update(密文) → doFinal 得到明文。
三、实现步骤
工程为 Stage 模型(API 12+),加解密属于应用内纯计算,无需任何权限配置,module.json5 保持默认。
1. 加解密工具类 CryptoUtil.ets
import { cryptoFramework } from '@kit.CryptoArchitectureKit';
import { util } from '@kit.ArkTS';
export interface GcmCipherResult {
cipherText: Uint8Array; // 密文(不含 tag)
authTag: Uint8Array; // 16 字节认证标签
iv: Uint8Array; // 本次加密使用的 iv,解密时需一并保存/传输
}
export class CryptoUtil {
// 随机生成 AES-128 对称密钥
static async generateAesKey(): Promise<cryptoFramework.SymKey> {
const generator = cryptoFramework.createSymKeyGenerator('AES128');
return await generator.generateSymKey();
}
// 从已有字节恢复密钥(如从 Asset Store / 服务端下发)
static async convertAesKey(raw: Uint8Array): Promise<cryptoFramework.SymKey> {
const generator = cryptoFramework.createSymKeyGenerator('AES128');
return await generator.convertKey({ data: raw });
}
// 构造 GCM 三要素:iv 随机 12 字节,aad 附加认证数据,tag 先占位 16 字节
private static genGcmParams(aad: Uint8Array): cryptoFramework.GcmParamsSpec {
const ivBlob = cryptoFramework.createRandom().generateRandomSync(12);
const tagBlob: cryptoFramework.DataBlob = { data: new Uint8Array(16) };
return {
iv: ivBlob,
aad: { data: aad },
authTag: tagBlob,
algName: 'GcmParamsSpec'
};
}
// 加密:返回密文 + tag + iv
static async encrypt(symKey: cryptoFramework.SymKey,
plainText: string, aad: Uint8Array): Promise<GcmCipherResult> {
const gcmParams = CryptoUtil.genGcmParams(aad);
const cipher = cryptoFramework.createCipher('AES128|GCM');
await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, symKey, gcmParams);
const plainBlob: cryptoFramework.DataBlob = {
data: new Uint8Array(util.TextEncoder.create('utf-8').encodeInto(plainText).buffer)
};
const encryptUpdate = await cipher.update(plainBlob);
// doFinal(null) 在 GCM 模式下返回 16 字节 authTag
gcmParams.authTag = (await cipher.doFinal(null)) as cryptoFramework.DataBlob;
return {
cipherText: encryptUpdate.data,
authTag: gcmParams.authTag.data,
iv: gcmParams.iv.data
};
}
// 解密:密文、tag、iv 齐全才能还原;tag 校验失败会抛异常
static async decrypt(symKey: cryptoFramework.SymKey,
result: GcmCipherResult, aad: Uint8Array): Promise<string> {
const gcmParams: cryptoFramework.GcmParamsSpec = {
iv: { data: result.iv },
aad: { data: aad },
authTag: { data: result.authTag },
algName: 'GcmParamsSpec'
};
const cipher = cryptoFramework.createCipher('AES128|GCM');
await cipher.init(cryptoFramework.CryptoMode.DECRYPT_MODE, symKey, gcmParams);
const decryptUpdate = await cipher.update({ data: result.cipherText });
await cipher.doFinal(null); // 校验 tag,篡改时此处抛 17630001
return util.TextDecoder.create('utf-8').decodeToString(decryptUpdate.data);
}
}
2. UI 调用示例 Index.ets
import { BusinessError } from '@kit.BasicServicesKit';
import { CryptoUtil, GcmCipherResult } from '../model/CryptoUtil';
@Entry
@Component
struct Index {
@State originText: string = '鸿蒙 AES-GCM 加密测试数据 2026';
@State cipherResult: GcmCipherResult | null = null;
@State decryptedText: string = '';
private symKey: cryptoFramework.SymKey | null = null;
// 一次性生成密钥,真实场景应持久化到 Asset Store Kit
private async ensureKey(): Promise<cryptoFramework.SymKey> {
if (!this.symKey) {
this.symKey = await CryptoUtil.generateAesKey();
}
return this.symKey;
}
doEncrypt(): void {
this.ensureKey()
.then((key) => CryptoUtil.encrypt(key, this.originText,
new Uint8Array(util.TextEncoder.create('utf-8').encodeInto('app-header').buffer)))
.then((r: GcmCipherResult) => {
this.cipherResult = r;
console.info(`encrypt ok, cipher len=${r.cipherText.length}, tag len=${r.authTag.length}`);
})
.catch((err: BusinessError) => console.error(`encrypt failed: ${err.code}`));
}
doDecrypt(): void {
if (!this.cipherResult) { return; }
this.ensureKey()
.then((key) => CryptoUtil.decrypt(key, this.cipherResult!,
new Uint8Array(util.TextEncoder.create('utf-8').encodeInto('app-header').buffer)))
.then((plain: string) => { this.decryptedText = plain; })
.catch((err: BusinessError) => {
if (err.code === 17630001) {
this.decryptedText = '解密失败:数据已被篡改';
} else {
this.decryptedText = `解密失败 code=${err.code}`;
}
});
}
build() {
Column({ space: 12 }) {
TextInput({ text: this.originText })
.onChange((v: string) => { this.originText = v; })
.width('90%')
Row({ space: 12 }) {
Button('加密').onClick(() => { this.doEncrypt(); })
Button('解密').onClick(() => { this.doDecrypt(); })
}
Text(this.cipherResult
? `密文(${this.cipherResult.cipherText.length}B): ${Array.from(this.cipherResult.cipherText.slice(0, 8)).toString()}...`
: '尚未加密')
.fontSize(13)
Text(`解密结果:${this.decryptedText || '尚未解密'}`)
.fontSize(15)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
四、应用场景示例
大文件(如聊天图片、离线包)需要分段加密时,多次 update 拼接即可,避免一次性占用大内存:
static async encryptBySegment(symKey: cryptoFramework.SymKey,
chunks: Uint8Array[], aad: Uint8Array): Promise<{ data: Uint8Array; tag: Uint8Array }> {
const gcmParams = CryptoUtil.genGcmParams(aad);
const cipher = cryptoFramework.createCipher('AES128|GCM|PKCS7');
await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, symKey, gcmParams);
const out: number[] = [];
for (const chunk of chunks) {
const r = await cipher.update({ data: chunk });
if (r) { out.push(...r.data); } // update 结果可能为 null,需判空后拼接
}
const tag = await cipher.doFinal(null);
return { data: new Uint8Array(out), tag: tag.data };
}
五、技术要点
1. authTag 的生命周期
GCM 加密输出 = 密文 + 16 字节 authTag,存储/传输时必须把 tag、iv 与密文一起保存;解密端三者缺一不可,iv 建议每次加密都重新随机生成(12 字节),绝不能复用同一 (key, iv) 组合加密两条消息。
2. AAD 参与认证但不加密
aad 中的数据不会出现在密文里,但一旦解密时传入的 AAD 与加密时不一致,doFinal 同样校验失败——适合把协议头、版本号等"需要保护但不保密"的字段放进去。
3. 错误处理
try {
await CryptoUtil.decrypt(key, result, aad);
} catch (err) {
const e = err as BusinessError;
if (e.code === 17630001) {
console.error('GCM tag 校验失败:密文/tag/aad 被篡改或与加密参数不一致');
} else if (e.code === 401) {
console.error('参数错误:检查算法字符串与 GcmParamsSpec.algName');
} else if (e.code === 801) {
console.error('设备不支持该算法规格,需降级方案');
}
}
4. 使用限制
- 算法字符串规格严格:
AES128|GCM、AES256|GCM合法,AES-GCM或aes128|gcm会报 401; update结果与doFinal结果都可能为 null,访问.data前必须判空;- 非对称加密(RSA)单次加解密数据量受密钥长度限制(如 RSA2048 单次最多 245 字节),大数据量请走"RSA 加密 AES 密钥 + AES 加密数据"的混合方案。
六、扩展建议
- 密钥安全存储:用 Asset Store Kit 把 AES 原始字节存入系统级资产库(
ASSET_TYPE_BYTES),运行时按需读取,避免密钥明文落盘; - 硬件级保护:接入 HUKS(Universal Keystore Kit)让密钥生成与加解密全部在 TEE/安全环境内完成,应用侧只拿句柄;
- 国密算法:同一套 API 换算法规格即可切换国密:
// SM4-GCM:与 AES 用法完全一致,仅算法字符串不同
const cipher = cryptoFramework.createCipher('SM4|GCM|PKCS7');
结语
本文用 cryptoFramework 完成了鸿蒙应用内 AES-128-GCM 从密钥生成、加密、认证标签管理到解密校验的完整实现,核心代码约 160 行,均可直接编译运行。掌握 GCM 的 tag/iv/AAD 三要素后,应用内的本地敏感数据落盘、接口报文加密都有了标准且经过认证加密保护的实现方案。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)