在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一、前置思考

数据泄露事故里,90% 的受害者都有一个共同点:敏感数据以明文存储。数据库文件被拖走、备份被还原、日志里躺着明文密码——这些都不是"黑客多高明",而是"加密没落地"。

我们先看一组真实的泄露路径,理解为什么"明文存储"是罪魁祸首:

泄露路径 攻击者操作 为什么明文致命
数据库拖走 root 后直接拷贝 .db 文件 明文数据直接可读
备份还原 恢复旧备份到另一台设备 敏感数据跟着备份走
日志泄露 抓取 logcat 日志 日志里躺着明文密码/Token
缓存外泄 读取 cache 目录 图片/JSON 明文缓存被拖
传输抓包 中间人解密流量 未加密的请求/响应明文可见

鸿蒙应用的数据保护需要回答三个问题:

  1. 本地数据:SQLite 数据库、Preferences、缓存文件如何加密?
  2. 密钥安全:加密密钥放在哪里才不会被逆向提取?
  3. 传输数据:网络请求如何防止中间人抓包篡改?

本文给出"本地加密 + HUKS 管密钥 + SSL Pinning"的三位一体方案。

二、核心原理

2.1 数据加密分层架构

┌─────────────────────────────────────────────┐
│            应用数据层                        │
│  SQLite DB · Preferences · 缓存文件 · 日志    │
├─────────────────────────────────────────────┤
│            加密引擎层                        │
│  AES-256 / SM4 · 字段级加密 · 全库加密         │
├─────────────────────────────────────────────┤
│            密钥管理层 (HUKS)                  │
│  数据库主密钥 · API签名密钥 · IV密钥            │
│  密钥存 TEE,永不出设备                       │
└─────────────────────────────────────────────┘

数据加密有三种粒度,需要根据场景选择:

(1)全库加密(Whole-DB Encryption)

整个数据库文件加密。优点是实施简单、覆盖全面;缺点是性能开销大(每条查询都要解密),且大表查询会明显变慢。适合数据量小、查询不频繁的场景。

(2)字段级加密(Field-Level Encryption)

只加密敏感字段(手机号、身份证、余额)。高频非敏感字段不加密,查询性能不受影响。缺点是实施复杂,需要维护字段级加密逻辑,且加密字段无法直接做 LIKE 模糊查询和索引。

(3)应用层加密 + 传输加密

数据在写入前由应用层加密,读取后解密;同时配合 TLS 传输加密。这是最灵活的方式,适合对数据流有完全控制权的场景。

推荐的混合策略:高频非敏感字段明文 + 敏感字段单独加密(字段级)+ 数据库文件整体加密兜底 + 密钥全部走 HUKS。

2.2 HUKS 密钥管理

import { huks } from '@kit.UniversalKeystoreKit';

export class HukKeyManager {
  // 生成数据库主密钥
  static async createDbMasterKey(alias: string): Promise<void> {
    const 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 }
    ];
    const options: huks.HuksOptions = { properties: properties };
    await huks.generateKeyItem(alias, options);
  }

  // 检查密钥是否存在
  static async hasKey(alias: string): Promise<boolean> {
    return await huks.hasKeyItem(alias);
  }

  // 删除密钥(数据作废/用户注销)
  static async deleteKey(alias: string): Promise<void> {
    await huks.deleteKeyItem(alias);
  }
}

密钥管理铁律:

  • 密钥不硬编码:绝不能把密钥字符串写死在代码里。反编译 APK 是最基本的攻击手段,硬编码密钥等于把保险柜钥匙贴在门上。
  • 密钥不入库:密钥只存 HUKS,数据库只存密文。数据库文件可以被拖走,但拖走的是密文;解密需要 HUKS 里的密钥,而密钥在 TEE 内,root 也拿不到。
  • 密钥可轮换:弱算法密钥(3DES/DES)识别后立即替换。轮换流程:生成新密钥 → 用新密钥解密旧数据 → 重新加密 → 删除旧密钥。

2.3 传输层 SSL Pinning

SSL Pinning(证书绑定)是防中间人攻击的关键:

客户端                             服务器
  │                                  │
  │ 1. TLS 握手                       │
  │ 2. 服务器下发证书                  │
  │ 3. 校验证书是否匹配内置指纹         │
  │    ├─ 匹配 → 建立安全连接          │
  │    └─ 不匹配 → 立即中断(防中间人) │
  │                                  │
  │ 4. 数据加密传输                    │
  ▼                                  ▼

为什么仅仅 HTTPS 不够? 攻击者可以安装自签名的"中间人证书"到设备信任库(在企业管控设备、Root 设备上很常见),或者通过代理工具(Charles/mitmproxy)动态签发证书。标准 HTTPS 只校验"证书链是否有效",不校验"证书是否是我认识的"。SSL Pinning 把校验从"证书链有效"升级为"证书必须是内置的那一个"——即使攻击者伪造了证书链,也会因为指纹不匹配而握手失败。

Pinning 两种模式:

模式 说明 适用场景
证书公钥绑定 内置服务器证书公钥 服务器证书固定
证书链绑定 内置根/中间 CA 指纹 证书轮换频繁
// Pinning 校验示意
class SslPinningValidator {
  // 内置服务器证书 SHA256 指纹白名单
  private static readonly PIN_HASHES: Array<string> = [
    '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=',
    '9U4HzfQYxQsKk8rN+8BvZmUyWlO7cDmXeFgHiJkLmNo='
  ];

  static validate(certFingerprint: string): boolean {
    for (let i: number = 0; i < this.PIN_HASHES.length; i++) {
      if (certFingerprint === this.PIN_HASHES[i]) {
        return true;
      }
    }
    return false;
  }
}

Pinning 的运维陷阱:证书轮换时如果只更新了服务端而没更新客户端白名单,会导致线上大面积连接失败。建议做法是"多 PIN 策略"——内置当前证书 + 备份证书的指纹,轮换时先让备份证书生效,再更新主证书。

三、典型攻击场景还原

3.1 场景一:拖库攻击

攻击者操作:
1. 通过备份/root 获取应用数据库文件
2. 用 SQLite 工具直接打开查看

被拦截的环节:
① 数据库已全库加密,打开看到的是乱码密文
② 密钥在 HUKS/TEE 内,攻击者无法在 TEE 外解密
③ 即使拿到 root,也无法导出 TEE 内密钥

3.2 场景二:中间人抓包

攻击者操作:
1. 设置代理,安装自签名证书
2. 抓取并解密应用流量

被拦截的环节:
① 标准 HTTPS + 证书链校验:自签名证书不在信任库,握手失败
② SSL Pinning:即使安装到信任库,指纹与内置白名单不符,连接中断
③ 请求签名校验:即使拿到明文流量,也无法伪造带签名的请求

3.3 场景三:逆向提取密钥

攻击者操作:
1. 反编译 APK,搜索密钥字符串
2. 从代码中提取硬编码密钥

被拦截的环节:
① 代码中没有任何明文密钥(全部走 HUKS)
② 即使反编译成功,HUKS 接口只接受 alias,不接受密钥本身
③ 密钥生成/使用都在 TEE 内,静态分析拿不到任何密钥材料

四、实战落地

对应 Demo 页面:entry/src/main/ets/pages/DataEncryptionDemo.ets

Demo 包含三个 Tab:

  1. 数据库加密:展示各数据表/缓存的加密算法与密钥强度,支持开关演示全库加密状态
  2. HUKS密钥:列出数据库主密钥/API签名密钥等,支持"轮换弱密钥"操作(模拟 3DES→AES-256 迁移)
  3. SSL Pinning:列出各域名的证书指纹与匹配状态,支持一键校验全部绑定
// Demo 核心:弱密钥轮换
private rotateLegacyKey(): void {
  const newKeys: KeyEntry[] = [];
  for (let i: number = 0; i < this.keys.length; i++) {
    if (this.keys[i].alias === 'legacy_key') {
      newKeys.push({
        alias: 'legacy_key (已迁移)', type: 'AES-256',
        protect: 'HUKS 设备级保护', status: '✅ 已轮换'
      });
    } else {
      newKeys.push(this.keys[i]);
    }
  }
  this.keys = newKeys;
}

4.1 字段级加密的实现模式

字段级加密是混合策略的核心,实现上有一个重要的工程决策:加密放在哪一层。三种常见模式:

模式 做法 优点 缺点
DAO 层加密 数据访问对象层统一加解密 业务无感 DAO 层易被绕过
Service 层加密 业务服务层处理 灵活控制粒度 各服务重复实现
数据库触发器加密 SQL 层面自动加解密 集中管理 性能开销大、难以调试

推荐的实践是 DAO 层统一封装:敏感字段的写入/读取全部收敛到一个加密 Repository,业务层只接触"明文",底层统一完成 HUKS 加解密。这样既保证加密不遗漏,又方便日后切换算法或调整字段清单。

// 加密 Repository 模式示意
export class SecureUserRepo {
  // 写入:内部自动加密
  static async savePhone(userId: number, phone: string): Promise<void> {
    const cipher: Uint8Array = await CryptoUtil.encryptByHuks('db_key', phone);
    await DbUtil.update('user', { phone_cipher: cipher }, { id: userId });
  }

  // 读取:内部自动解密
  static async getPhone(userId: number): Promise<string> {
    const row = await DbUtil.query('user', { id: userId });
    return await CryptoUtil.decryptByHuks('db_key', row.phone_cipher);
  }
}

字段级加密还需要考虑可搜索性问题:加密字段无法直接 LIKE 查询。工程上的两个折中方案:

  • 可搜索加密:对敏感字段拆出"可索引摘要"(如手机号后四位),查询时先用摘要索引缩小范围,再解密精确匹配。
  • 业务层二次索引:维护一张"明文指纹 → 密文记录"的映射表,查询走指纹索引。

4.2 完整落地清单

数据库层

  • 敏感表启用字段级加密,高频查询字段保持明文
  • 数据库文件整体加密(可选,视性能要求)
  • 连接池内密钥不缓存,每次使用从 HUKS 取

Preferences 层

  • 敏感键值(Token/密码)单独加密存储
  • 非敏感配置明文存储(首选项本身性能优先)

文件层

  • 敏感导出文件(CSV/PDF)加密后落盘
  • 日志一律脱敏(手机号/身份证打码)

网络层

  • 全流量 HTTPS,禁用明文 HTTP
  • SSL Pinning 双证书策略
  • 请求签名 + 时间戳防重放

密钥层

  • 所有密钥 HUKS 管理,代码零密钥
  • 密钥轮换机制上线
  • 卸载重装策略明确(云备份或重新生成)

五、避坑速查

现象 原因 解决
密钥硬编码 反编译直接拿到密钥 把密钥写死在代码 全部走 HUKS,代码零密钥
加密后性能暴跌 大表查询卡顿 全库加密每行解密 按字段分级:高频字段不加密,敏感字段单独加密
Pinning 失效 抓包工具成功解密 证书轮换后指纹未更新 维护指纹白名单 + 远程配置兜底
密钥与安装绑定 卸载重装数据丢失 HUKS 密钥随安装删除 明确告知用户,提供数据导出
混合内容 HTTPS 页面加载 HTTP 资源 证书校验被绕过 WebView 禁止 mixed content
国密需求 等保要求 SM 系列 项目用了 AES 加密引擎抽象层同时支持 AES/SM4
加密字段无法查询 业务功能报错 字段加密后无法 LIKE/索引 采用可搜索加密方案或业务层二次索引
密钥轮换丢数据 轮换后旧数据读不出 未按序迁移 轮换流程:新钥解密→重加密→删旧钥
IV 复用 相同明文产生相同密文 IV 固定 每次加密生成随机 IV,IV 与密文同存
Pinning 单证书 轮换后大面积断连 只内置一个指纹 双证书策略,轮换无缝切换

六、总结

数据加密不是"加一层壳",而是系统工程:

  1. 静态数据:SQLite/缓存全加密,密钥走 HUKS(TEE 保护)
  2. 动态数据:传输层 TLS + Pinning 双重校验
  3. 密钥生命周期:生成→使用→轮换→删除,全程可管控

落地建议:

  • 加密引擎做抽象层(AES/SM4 可切换),满足国密合规
  • 密钥轮换要有停机窗口设计,规避旧版本兼容问题
  • Pinning 指纹用远程配置下发,兼顾安全与运维灵活性
  • 加密性能要有基准测试数据,防止加密成为性能瓶颈
  • 定期审计密钥使用情况,及时清理弱算法残留
Logo

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

更多推荐