[鸿蒙从零到一] HarmonyOS 安全加固与数据保护实战:从密钥管理到反调试的全链路防护

在应用被逆向、数据被窃取的案例层出不穷的今天,安全不再是上线前"补一刀"的可选项,而是架构设计阶段就要考虑的基础能力。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;
  }
}

几个实战要点:

  • ACCESSIBILITYDEVICE_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、把数据库加密打开,这两步成本最低、收益最大,然后再逐步推进证书锁定与代码加固。

相关推荐
用户921080262861 小时前
从 Bubble 到 BubbleList:加载历史对话时才真正看懂组件通信
前端
hunterandroid1 小时前
Android WebView JSBridge 治理实战:从线上白屏崩溃到协议化通信
android·前端
意疏1 小时前
2026年远控软件安全横评:六款主流工具逐项核查——官方文档、一手实测与安全事件,全摊开
大数据·前端·数据库
梦曦i1 小时前
RouterLink H5端控制台错误修复
前端·uni-app
qq_548612451 小时前
.NET 平台报表工具汇总(.NET Framework /.NET6-8,中国式复杂报表、Web 嵌入、填报、导出打印)
前端·.net
懒狗小前端2 小时前
自嗨不如一起嗨
前端
郑州光合科技余经理2 小时前
海外版多语言团购系统架构:主数据互通与核销边界
java·开发语言·前端·后端·系统架构·php·ai编程
达令哥2 小时前
告别 ARouter!基于 Google 官方 Navigation 3 + KSP 打造 Compose 时代的双轨制路由框架
android·前端