鸿蒙原生密码保险箱 PasswordVault 应用开发实战

鸿蒙原生密码保险箱 PasswordVault 应用开发实战

基于 ArkTS + SQLite + AES-256-GCM 构建的个人密码安全管家


一、项目概览

PasswordVault 是一款运行在鸿蒙(HarmonyOS)原生平台上的个人密码保险箱应用。它使用纯 ArkTS 声明式 UI 框架构建界面,借助 @ohos.data.relationalStore(SQLite)实现本地数据持久化,通过 @ohos.security.cryptoFramework 提供的 AES-256-GCM 加密算法保护用户密码,并集成了系统剪贴板实现一键复制功能。

技术栈一览:

技术组件 用途
ArkTS + @State 响应式框架 构建声明式 UI
@ohos.data.relationalStore SQLite 关系型数据库
@ohos.security.cryptoFramework AES-256-GCM 加解密
@ohos.pasteboard 系统剪贴板服务
@ohos.util TextEncoder/TextDecoder 编解码
API Version 24+ 鸿蒙原生 API

架构层次:

复制代码
表示层 (PasswordVault / PasswordEditDialog / PasswordDetailDialog)
       ↓
业务逻辑层 (CRUD 编排 / 搜索筛选 / 剪贴板)
       ↓
加密层 (CryptoUtil --- AES-256-GCM)
       ↓
数据层 (PasswordDatabase --- SQLite)

以"新增密码"为例的完整数据流:用户填写表单 → 点击确认 → addPassword() 调用 vaultDB.insert() → 内部先 cryptoUtil.encrypt() 加密明文 → 加密密文写入 SQLite → 成功后重新 loadData() → 逐条 cryptoUtil.decrypt() 解密 → @State displayList 更新,UI 自动渲染。


二、数据模型设计

2.1 分类定义

应用预定义了 7 个密码分类,每个含名称、Emoji 图标和主题色:

分类 图标 主题色
社交媒体 💬 #8E74FF
电子邮件 ✉️ #007AFF
金融财务 💰 #34C759
购物消费 🛒 #FF9500
工作办公 💼 #FF3B30
娱乐影音 🎵 #FF2D55
其他 📁 #8E8E93

2.2 三层数据模型

PasswordItem(存储模型)------对应数据库表记录:

typescript 复制代码
interface PasswordItem {
  id: number;                // 自增主键
  category: string;          // 分类
  appName: string;           // 应用名称
  username: string;          // 账户名
  encryptedPassword: string; // AES 加密后的 Base64 密文
  notes: string;             // 备注
  createTime: string;        // 创建时间 ISO 8601
  updateTime: string;        // 更新时间 ISO 8601
}

InsertPasswordItem(输入模型)------新增/修改时使用,包含明文密码:

typescript 复制代码
interface InsertPasswordItem {
  category: string;
  appName: string;
  username: string;
  password: string;  // 明文,传入后加密存储
  notes: string;
}

PasswordDisplayItem(展示模型)------界面渲染使用,增加 UI 状态:

typescript 复制代码
interface PasswordDisplayItem {
  // ...同 PasswordItem 字段
  password: string;       // 解密后的明文
  showPassword: boolean;  // 控制密码明文显隐
}

三、数据库实现(PasswordDatabase)

3.1 初始化

使用单例模式(const vaultDB = new PasswordDatabase()),确保全局只有一个数据库连接:

typescript 复制代码
async init(context: Context): Promise<void> {
  if (this.rdbStore) return;
  const config: relationalStore.StoreConfig = {
    name: 'password_vault.db',
    securityLevel: relationalStore.SecurityLevel.S1, // 敏感信息等级
  };
  this.rdbStore = await relationalStore.getRdbStore(context, config);
  await this.createTable();
}

SecurityLevel.S1 告知系统该数据库包含个人敏感信息,系统会启用更严格的数据保护。

3.2 表结构

sql 复制代码
CREATE TABLE IF NOT EXISTS passwords (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  category TEXT NOT NULL,
  appName TEXT NOT NULL,
  username TEXT NOT NULL,
  encryptedPassword TEXT NOT NULL,
  notes TEXT DEFAULT '',
  createTime TEXT DEFAULT '',
  updateTime TEXT DEFAULT ''
)

3.3 CRUD 操作

插入------三步走:校验必填字段 → AES 加密密码 → 写入数据库:

typescript 复制代码
async insert(item: InsertPasswordItem): Promise<number> {
  this.validateInsert(item);  // 校验 appName/username/password/category
  const encryptedPwd = await cryptoUtil.encrypt(item.password);
  const now = new Date().toISOString();
  const rowId = await this.rdbStore.insert(TABLE_NAME, {
    category: item.category,
    appName: item.appName.trim(),
    username: item.username.trim(),
    encryptedPassword: encryptedPwd,
    notes: item.notes.trim(),
    createTime: now,
    updateTime: now,
  });
  return rowId;
}

查询 ------三种模式:全量 queryAll()(按 updateTime 降序)、按分类 queryByCategory(category)(精确匹配)、模糊搜索 queryBySearch(keyword)(对 appNameusernamenotes 三字段 LIKE 匹配):

typescript 复制代码
async queryBySearch(keyword: string): Promise<PasswordItem[]> {
  const predicates = new relationalStore.RdbPredicates(TABLE_NAME);
  predicates.like('appName', `%${keyword}%`).or()
    .like('username', `%${keyword}%`).or()
    .like('notes', `%${keyword}%`);
  predicates.orderByDesc('updateTime');
  // 执行查询...
}

更新 ------先加密新密码,再按 id 精确更新,保留 createTime 不变:

typescript 复制代码
async update(id: number, item: InsertPasswordItem): Promise<void> {
  const encryptedPwd = await cryptoUtil.encrypt(item.password);
  const predicates = new relationalStore.RdbPredicates(TABLE_NAME);
  predicates.equalTo('id', id);
  await this.rdbStore.update({ ...value, updateTime: new Date().toISOString() }, predicates);
}

删除------按主键删除,简单直接。


四、AES-256-GCM 加密实现(CryptoUtil)

4.1 算法选型

特性 说明
密钥长度 256 位 业界最高强度对称加密
GCM 认证加密模式 同时提供机密性和完整性校验
12 字节随机 IV 相同明文每次产生不同密文
鸿蒙原生支持 系统级安全组件,经过安全审计

4.2 密钥初始化

当前版本使用固定演示密钥(生产环境应使用生物识别/主密码派生):

typescript 复制代码
// 生产环境应使用 PBKDF2/Argon2 从用户主密码派生
const AES_KEY_B64 = 'dGVzdC1rZXktMzItYnl0ZXMtZm9yLWRlbW8tYWFhYQ==';

private async initKey(): Promise<void> {
  const keyDataBin = this.base64ToBytes(AES_KEY_B64);
  const symKeyGenerator = cryptoFramework.createSymKeyGenerator('AES256');
  this.keyBlob = await symKeyGenerator.convertKey({ data: keyDataBin });
}

4.3 加密流程

复制代码
明文 → AES-256-GCM 加密 → IV(12B) + 密文(N B) + AuthTag(16B) → Base64 编码 → 存储字符串

核心实现:

typescript 复制代码
async encrypt(plainText: string): Promise<string> {
  await this.assertKeyReady();
  const ivData = this.generateRandomBytes(12);
  const authTagBuf = new Uint8Array(16);
  const gcmParams: GcmParamsSpec = {
    iv: { data: ivData },
    aad: { data: new Uint8Array(0) },
    authTag: { data: authTagBuf },
    algName: 'GCM',
  };
  const cipher = cryptoFramework.createCipher('AES256|GCM|PKCS7');
  await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, this.keyBlob!, gcmParams);
  const cipherBlob = await cipher.update({ data: this.stringToBytes(plainText) });
  const finalBlob = await cipher.doFinal(null);
  // doFinal 后 gcmParams.authTag 被框架填充实际认证标签
  const combined = this.concatUint8Array(
    this.concatUint8Array(ivData, cipherBlob.data, finalBlob.data),
    gcmParams.authTag.data,
  );
  return this.bytesToBase64(combined);
}

4.4 解密流程

解密是加密的逆过程:Base64 解码 → 拆分 IV(前12B) + 认证标签(后16B) + 密文(中间) → 构造 GCM 参数 → 解密。

若数据被篡改导致认证标签校验失败,框架会抛出异常。解密失败时返回 '*** 解密失败 ***' 而非抛异常,避免单条数据问题导致整个列表无法渲染。

4.5 关键工具函数

因 ArkTS 无浏览器标准 atob/btoa,手动实现 Base64 编解码。配合 util.TextEncoderstringToBytes)和 util.TextDecoderbytesToString)完成文本与字节数组的互转。


五、UI 组件详解

5.1 PasswordVault 主页面(@Entry @Component

作为应用入口页面(EntryAbility.etsloadContent('pages/PasswordVault')),管理全局状态:

typescript 复制代码
struct PasswordVault {
  @State displayList: PasswordDisplayItem[] = [];
  @State isLoading: boolean = true;
  @State searchKeyword: string = '';
  @State selectedCategory: string = '';   // '' = 全部
  @State showCategoryPicker: boolean = false;
  private rawItems: PasswordItem[] = [];   // 原始数据缓存
}

页面布局(自上而下):

  1. 顶部导航栏:标题"密码保险箱" + 新增按钮
  2. 搜索栏:关键词实时搜索
  3. 分类筛选栏:横向滚动的标签选择器
  4. 密码列表:List + ForEach 渲染密码卡片
  5. 底部统计:总条目数

生命周期与数据加载:

typescript 复制代码
aboutToAppear(): void { this.initDatabase(); }

private async initDatabase(): Promise<void> {
  const context = getContext(this);
  await vaultDB.init(context);
  await this.loadData();
  this.isLoading = false;
}

private async loadData(): Promise<void> {
  let items: PasswordItem[];
  if (this.selectedCategory && this.selectedCategory !== '全部')
    items = await vaultDB.queryByCategory(this.selectedCategory);
  else if (this.searchKeyword.trim())
    items = await vaultDB.queryBySearch(this.searchKeyword.trim());
  else
    items = await vaultDB.queryAll();
  this.rawItems = items;
  await this.rebuildDisplayList(items);
}

逐条解密并构建展示列表:

typescript 复制代码
private async rebuildDisplayList(items: PasswordItem[]): Promise<void> {
  const displayList: PasswordDisplayItem[] = [];
  for (const item of items) {
    const decrypted = await cryptoUtil.decrypt(item.encryptedPassword);
    displayList.push({
      id: item.id, category: item.category, appName: item.appName,
      username: item.username, password: decrypted, notes: item.notes,
      createTime: item.createTime, updateTime: item.updateTime, showPassword: false,
    });
  }
  this.displayList = displayList;
}

5.2 PasswordEditDialog(新增/编辑弹窗)

@CustomDialog 组件,通过 editItem 参数区分新增/编辑模式:

  • 新增模式editItem = undefined):所有字段为空
  • 编辑模式editItem = PasswordDisplayItem):自动填充已有数据

表单字段包含应用名称、账户名、密码、分类选择、备注。密码输入支持显示/隐藏切换(InputType.Password / InputType.Normal 动态切换)。

实时校验反馈: 每个输入框关联 @State xxxError 错误状态,用户输入时自动清除错误;提交时若存在空字段,对应位置显示红色错误提示。

弹窗使用 DialogAlignment.Bottom 配置为底部弹出样式,内容区域由 Scroll 包裹以支持滚动。

5.3 PasswordDetailDialog(详情弹窗)

展示密码完整信息,默认隐藏密码明文(显示 ••••••••••••),支持:

  • 复制密码 :调用 pasteboard.getSystemPasteboard().setData(data) 写入系统剪贴板
  • 显示/隐藏 :点击眼睛图标 👁️/🙈 切换明文
  • 编辑:关闭详情,打开编辑弹窗并预填数据
  • 删除AlertDialog 二次确认,红色警示"此操作不可恢复"

5.4 密码卡片(buildPasswordCard)

每张卡片采用白色圆角 + 细阴影的 Material 风格:

复制代码
┌──────────────────────────────────┐
│ 💬 社交媒体   微信             📋 │
│ 👤 user@example.com           👁️ │
│ 🔑 ••••••••••••                  │
│ 06月05日 10:48                   │
└──────────────────────────────────┘

点击卡片主体 → 打开详情;点击复制按钮 → 复制密码(阻止冒泡);点击显隐按钮 → item.showPassword = !item.showPassword; this.displayList = [...this.displayList](展开运算符触发 @State 更新)。


六、搜索与分类筛选

搜索和分类筛选共用 loadData() 方法,优先级:分类筛选 > 关键词搜索 > 全量查询

每个分类标签显示 Emoji + 名称,选中态使用主题色高亮:

typescript 复制代码
Text(`${cat.icon} ${cat.name}`)
  .fontColor(this.selectedCategory === cat.name ? cat.color : '#8E8E93')
  .backgroundColor(this.selectedCategory === cat.name ? cat.color + '20' : '#F2F2F7')

交互细节:再次点击已选分类可取消筛选;筛选变化时显示 loading 状态。


七、安全设计

7.1 当前措施

措施 实现
密码加密存储 AES-256-GCM 加密后写入数据库
认证加密 GCM 模式同时保机密性和完整性
随机 IV 12 字节随机数,相同明文每次密文不同
敏感信息分级 数据库 SecurityLevel.S1
删除确认 AlertDialog 二次确认
密码默认隐藏 列表和详情页均以密文显示

7.2 生产环境改进建议

  1. 主密码验证:应用启动时验证用户主密码或生物识别
  2. 密钥派生:使用 PBKDF2/Argon2 从主密码派生 AES 密钥
  3. HUKS 密钥存储:利用鸿蒙通用密钥库,密钥不出安全硬件
  4. 自动锁定:后台切换超时后自动锁定
  5. 剪贴板自动清除:复制密码后定时清除剪贴板内容

八、开发环境与运行配置

应用包名 com.example.demo0528,版本 1.0.0,仅支持 Phone 设备。页面路由注册在 main_pages.json

json 复制代码
{ "src": ["pages/Index", "pages/TodoList", "pages/PasswordVault"] }

运行要求:API 24+、HarmonyOS 真机或模拟器、DevEco Studio 开发工具。


九、总结与展望

PasswordVault 展示了鸿蒙原生开发的完整技术实践:

  • ArkTS 响应式 UI@State + @Component + @CustomDialog 构建声明式界面
  • SQLite 数据持久化relationalStore 实现本地 CRUD 和条件检索
  • AES-256-GCM 加密cryptoFramework 实现工业级密码保护
  • 系统服务集成pasteboard 实现剪贴板复制
  • 安全设计:多层次的存储加密和操作防护

可扩展方向

  • 数据加密备份与恢复
  • 强密码生成器
  • 密码健康检查(弱密码/重复密码检测)
  • 鸿蒙 AutoFill 自动填充
  • 端到端加密云同步
  • 深色模式适配

本文基于 PasswordVault.ets 源码(1563 行)撰写,完整源码位于 entry/src/main/ets/pages/PasswordVault.ets

相关推荐
小雨下雨的雨1 小时前
基于 Electron 运行时的鸿蒙PC桌面应用-安全可靠的随机密码生成工具
前端·javascript·华为·electron·前端框架·鸿蒙
科技与数码1 小时前
鸿蒙AI防诈能力:场景化防诈+换脸检测+亲情防诈
人工智能·华为·harmonyos
小菜鸟学开发2 小时前
OpenHarmony v6.1-release 编译指南
harmonyos
李二。2 小时前
鸿蒙 HarmonyOS 待办清单应用开发实战 —— 基于 ArkTS + SQLite 的完整教程
华为·sqlite·harmonyos
Pocker_Spades_A2 小时前
[鸿蒙PC命令行移植适配]移植rust三方库peep到鸿蒙PC的完整实践
华为·rust·harmonyos
EterNity_TiMe_2 小时前
[鸿蒙PC命令行移植适配]移植rust三方库grex到鸿蒙PC的完整实践
华为·rust·harmonyos
EterNity_TiMe_2 小时前
[鸿蒙PC命令行移植适配]移植rust三方库lsd到鸿蒙PC的完整实践
华为·rust·harmonyos
IT大白鼠3 小时前
BGP协议概述:定义、机制与应用
网络·网络协议·华为
●VON3 小时前
AtomGit Flutter鸿蒙客户端:API客户端与网络层
flutter·华为·架构·跨平台·harmonyos·鸿蒙