在应用被逆向、数据被窃取的案例层出不穷的今天,安全不再是上线前"补一刀"的可选项,而是架构设计阶段就要考虑的基础能力。HarmonyOS 在系统层提供了相当完整的安全设施:HUKS 密钥管理、Asset 关键资产存储、加密算法框架、代码混淆与完整性校验等。本文从实战出发,把这些能力串成一条完整的数据保护链路:敏感数据怎么存、密钥怎么管、传输怎么防、代码怎么护。
一、先想清楚:要保护什么
动手写代码之前,先做数据分类。一个典型应用中的数据大致分四档:
- 关键凭据:登录 token、支付密码、生物识别关联凭据 ------ 泄露即出事
- 敏感个人数据:手机号、身份证号、位置轨迹 ------ 受法规约束
- 业务数据:订单、聊天记录、草稿 ------ 需要防篡改与访问控制
- 普通数据:UI 偏好、缓存 ------ 明文存储即可
不同档位对应不同的存储方案,这是后面所有技术选型的依据。把所有数据一股脑 AES 加密,或者把 token 明文丢进 Preferences,都是典型的分级缺失。
二、关键凭据:用 Asset Store Kit 存储
很多开发者习惯把 token 存进 Preferences,这在 HarmonyOS 上不是最优解。系统提供了专门的关键资产存储服务(Asset Store Kit),底层由系统级加密保护,支持"仅解锁后可访问""绑定生物认证"等访问控制策略。
typescript
import { asset } from '@kit.AssetStoreKit';
import { util } from '@kit.ArkTS';
function stringToArray(str: string): Uint8Array {
return new util.TextEncoder().encodeInto(str);
}
async function saveToken(token: string): Promise<void> {
const attr: asset.AssetMap = new Map();
attr.set(asset.Tag.SECRET, stringToArray(token));
attr.set(asset.Tag.ALIAS, stringToArray('user_token'));
// 仅在设备首次解锁后可访问
attr.set(asset.Tag.ACCESSIBILITY, asset.Accessibility.DEVICE_FIRST_UNLOCKED);
// 应用卸载后自动清除
attr.set(asset.Tag.IS_PERSISTENT, false);
try {
await asset.add(attr);
} catch (err) {
// 已存在则走更新逻辑
if (err.code === 24000003) {
await updateToken(token);
}
}
}
async function readToken(): Promise<string | null> {
const query: asset.AssetMap = new Map();
query.set(asset.Tag.ALIAS, stringToArray('user_token'));
query.set(asset.Tag.RETURN_TYPE, asset.ReturnType.ALL);
try {
const res = await asset.query(query);
const secret = res[0].get(asset.Tag.SECRET) as Uint8Array;
return new util.TextDecoder().decodeToString(secret);
} catch {
return null;
}
}
几个实战要点:
ACCESSIBILITY选DEVICE_FIRST_UNLOCKED是多数场景的平衡点:开机未解锁时不可读,但后台任务可用- 需要更强保护时,用
REQUIRE_PASSWORD_SET或绑定生物认证(AUTH_TYPE),支付类凭据建议上认证绑定 - Asset 有容量与条目限制,只放"凭据级"数据,不要当通用加密库用
三、业务数据加密:HUKS 管钥匙,cryptoFramework 干活
对需要加密的业务数据,正确姿势是密钥永远不落地:密钥生成、存储、使用全部在 HUKS(HarmonyOS Universal KeyStore)内完成,应用只拿句柄,拿不到密钥原文。
3.1 用 HUKS 生成 AES 密钥
typescript
import { huks } from '@kit.UniversalKeystoreKit';
const KEY_ALIAS = 'biz_data_key';
function getAesGenProperties(): 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_BLOCK_MODE, value: huks.HuksCipherMode.HUKS_MODE_GCM },
{ tag: huks.HuksTag.HUKS_TAG_PADDING, value: huks.HuksKeyPadding.HUKS_PADDING_NONE },
];
}
async function ensureKey(): Promise<void> {
const exist = await huks.isKeyItemExist(KEY_ALIAS, { properties: [] })
.catch(() => false);
if (!exist) {
await huks.generateKeyItem(KEY_ALIAS, { properties: getAesGenProperties() });
}
}
3.2 AES-GCM 加解密
typescript
async function encrypt(plain: Uint8Array, nonce: Uint8Array): Promise<Uint8Array> {
const props: huks.HuksParam[] = [
{ tag: huks.HuksTag.HUKS_TAG_ALGORITHM, value: huks.HuksKeyAlg.HUKS_ALG_AES },
{ tag: huks.HuksTag.HUKS_TAG_PURPOSE, value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT },
{ tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256 },
{ tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, value: huks.HuksCipherMode.HUKS_MODE_GCM },
{ tag: huks.HuksTag.HUKS_TAG_PADDING, value: huks.HuksKeyPadding.HUKS_PADDING_NONE },
{ tag: huks.HuksTag.HUKS_TAG_NONCE, value: nonce },
];
const session = await huks.initSession(KEY_ALIAS, { properties: props });
const result = await huks.finishSession(session.handle, { properties: props, inData: plain });
return result.outData as Uint8Array;
}
要点:
- 模式选 GCM,自带完整性校验,防篡改;CBC + 无 MAC 的组合等于裸奔
- nonce 每次加密必须随机新生成,与密文一起存储,复用 nonce 会直接破坏 GCM 安全性
- 随机数用
cryptoFramework.createRandom(),不要用Math.random() - 大文件加密走
updateSession分段处理,避免一次性把整个文件读进内存
3.3 数据库与 Preferences 的加密选项
relationalStore 支持开箱即用的加密,一行配置的事情不要自己造轮子:
typescript
const config: relationalStore.StoreConfig = {
name: 'app.db',
securityLevel: relationalStore.SecurityLevel.S3,
encrypt: true, // 数据库文件加密
};
securityLevel(S1~S4)决定数据的安全分级,影响分布式场景下能否跨设备同步------高安全级数据不会流向低安全级设备。这是 HarmonyOS 特有的数据流转管控,分布式应用一定要正确标注。
四、传输安全:HTTPS 之上再加一道锁
HTTPS 是底线,但公共 Wi-Fi 下的中间人攻击、用户手动安装的代理证书,都可能让"合法"的 TLS 被旁路。对金融、账号类接口,建议加证书锁定(Certificate Pinning):
json5
// src/main/resources/rawfile/untrusted_certs.json (网络配置)
{
"network-security-config": {
"domain-config": [
{
"domains": [ { "include-subdomains": true, "name": "api.example.com" } ],
"pin-set": {
"expiration": "2027-08-01",
"pin": [
{ "digest-algorithm": "sha256", "digest": "base64编码的公钥哈希==" }
]
}
}
]
}
}
实战建议:
- 锁公钥而不是锁证书,证书轮换不影响锁定
- pin 至少配两个:当前证书 + 备用证书,避免换证书导致全量用户接口不通
- 设置
expiration,过期后降级为普通校验,给自己留退路 - 客户端永远不要信任
rejectUnauthorized: false之类的"临时方案"进主干
五、代码保护:混淆、完整性与反调试
5.1 开启代码混淆
DevEco Studio 的 release 构建默认支持 ArkTS 字节码混淆,在模块的 build-profile.json5 中确认:
json5
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": true,
"files": ["./obfuscation-rules.txt"]
}
}
}
obfuscation-rules.txt 中按需开启更强的选项:
text
-enable-property-obfuscation
-enable-toplevel-obfuscation
-enable-filename-obfuscation
# 反射访问、SO 桥接的符号要 keep
-keep-global-name
MyNativeBridge
混淆后务必留存 nameCache 映射文件,否则线上崩溃堆栈没法还原------这一步和崩溃分析体系是配套的。
5.2 运行时自检
对安全敏感的应用,可以在启动时做环境自检:
typescript
import { bundleManager } from '@kit.AbilityKit';
async function selfCheck(): Promise<boolean> {
const info = await bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_SIGNATURE_INFO);
const fingerprint = info.signatureInfo.fingerprint;
// 与预置指纹比对,发现重签名直接拒绝运行核心功能
return fingerprint === EXPECTED_FINGERPRINT;
}
配合服务端做二次校验(把签名指纹随登录请求上报,服务端比对)比纯本地判断更可靠------本地判断逻辑本身也可能被篡改。
六、隐私合规:技术之外的必修课
安全加固的最后一环是合规:
- 最小化采集 :能不用敏感权限就不用,位置能用模糊定位(
APPROXIMATELY_LOCATION)就别上精准定位 - 日志脱敏:手机号打码、token 严禁进日志,release 构建关闭 debug 级日志输出
- 剪贴板与截屏 :密码输入组件用系统的安全输入能力,敏感页面可通过
window.setWindowPrivacyMode(true)防截屏录屏 - 数据删除 :提供账号注销与数据清除能力,Asset 的
IS_PERSISTENT设为 false 保证卸载即清
七、一张清单收尾
| 环节 | 方案 | 关键点 |
|---|---|---|
| 凭据存储 | Asset Store Kit | 访问控制策略 + 卸载清除 |
| 业务数据加密 | HUKS + AES-GCM | 密钥不出 TEE,nonce 随机 |
| 数据库 | relationalStore encrypt | securityLevel 正确标注 |
| 传输 | HTTPS + 证书锁定 | 锁公钥、配备用 pin |
| 代码 | 混淆 + 签名自检 | keep 规则 + nameCache 留存 |
| 隐私 | 最小化 + 脱敏 + 防截屏 | privacyMode、日志治理 |
安全是体系不是功能:存储、传输、代码、合规四条线都补齐,才算真正把用户数据放进了保险箱。建议从"数据分类"开始落地,先把关键凭据迁到 Asset、把数据库加密打开,这两步成本最低、收益最大,然后再逐步推进证书锁定与代码加固。