安全与隐私设计
爱生活 采用三层纵深安全防护架构,从应用入口到数据存储逐层加固,确保用户数据仅且只能由用户本人访问。
1. 安全架构总览
┌─────────────────────────────────────────────────────────────┐
│ 第一层:应用入口防护 │
│ 隐私协议同意 → 屏幕截图/录屏保护 → 后台自动锁屏 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 第二层:身份认证层 │
│ 加密密码 / PIN 码 ←→ 生物认证 (Face ID / Touch ID / 指纹) │
│ 自定义安全键盘输入 │ 5 档自动锁定时间 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 第三层:数据加密层 │
│ AES-256-GCM (原生) / AES-256-CBC + HMAC-SHA256 (降级) │
│ PBKDF2 密钥派生 (SHA-256, 10,000 次迭代) │
│ 数据库透明加密 (EncryptedStore) │
└─────────────────────────────────────────────────────────────┘设计原则:
- 零知识架构:加密密钥仅存于用户脑中(密码)和设备安全硬件(生物认证),服务端无法解密
- 透明加密:业务代码零感知,
EncryptedStore自动完成序列化 → 加密 → 存储 → 解密全流程 - 纵深防御:任一单层被突破,后续层级仍可保护数据安全
- 本地优先:全部数据存储在本地,云端同步采用端到端加密
2. 加密体系
/src/utils/crypto.ts — 核心加密模块,提供跨端统一的加解密能力。
2.1 密钥派生
用户密码通过 PBKDF2 派生为 256 位 AES 密钥:
| 参数 | 值 | 说明 |
|---|---|---|
| 算法 | PBKDF2-HMAC-SHA256 | 业界标准密钥派生函数 |
| 迭代次数 | 10,000 | 移动端约 50ms,兼顾安全与体验 |
| 密钥长度 | 256 bit (32 bytes) | AES-256 所需长度 |
| Salt 长度 | 256 bit (32 bytes) | 随机生成,存储于加密配置中 |
每次设置密码时随机生成新的 Salt,同一密码在不同设备上产生不同密钥。
2.2 加密算法
系统在两种环境下自动选择最优算法:
| 环境 | 加密算法 | 认证方式 | IV 长度 |
|---|---|---|---|
原生环境(有 crypto.subtle) | AES-256-GCM | GCM 内置认证标签 | 12 bytes |
| 降级环境(小程序等) | AES-256-CBC | HMAC-SHA256 | 16 bytes |
AES-256-GCM(首选):提供认证加密(AEAD),同时保证机密性和完整性,是目前最安全的对称加密模式之一。
AES-256-CBC + HMAC-SHA256(降级):当原生 crypto.subtle 不可用时(如部分小程序环境),使用 crypto-js 库实现。加密后额外计算 HMAC 校验值防止篡改。
2.3 随机数生成
- 优先:
crypto.getRandomValues()— 密码学安全的伪随机数生成器 - 降级:
Math.random()— 仅在不支持 CSPRNG 的极端环境下使用
2.4 加密/解密流程
加密:plaintext → JSON.stringify → crypto.encrypt(key, iv) → { _enc, _iv, _algo, _mac? }
解密:{ _enc, _iv, _algo, _mac? } → crypto.decrypt(key, iv) → JSON.parse → plaintext加密后的密文包含完整的元信息(IV、算法标识、可选的 HMAC),解密时无需额外配置。
3. 透明加密存储
/src/database/engines/encrypted-store.ts — 数据库透明加密封装层,对上层业务完全透明。
3.1 设计理念
业务代码调用 store.put(data)
↓
EncryptedStore 拦截
↓
JSON.stringify(data) → AES 加密 → 写入底层存储
↓
底层存储中仅存 { id, _enc, _iv, _algo, _mac? }
↓
业务代码调用 store.get(id)
↓
EncryptedStore 拦截
↓
从底层读取 → AES 解密 → JSON.parse → 返回原始对象业务层完全无感知,Repository 不包含任何加密逻辑。
3.2 加密记录格式 (EncRecord)
每条数据库记录在磁盘上的格式:
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 主键,保持明文以支持索引查询 |
_enc | string | 密文(Base64 编码) |
_iv | string | 初始化向量(Base64 编码) |
_algo | string | 算法标识:gcm 或 cbc |
_mac | string? | HMAC 校验值(仅 CBC 模式,可选) |
id 保持明文是有意设计:支持 where('id', value) 的索引快速查找,同时 id 是 UUID,不包含敏感信息。
3.3 EncryptedStore 接口
核心类实现 IStore<T> 接口:
| 方法 | 说明 |
|---|---|
put(item) | 加密后写入单条记录 |
get(id) | 读取并解密单条记录 |
bulkPut(items) | 批量加密写入 |
toArray() | 全量读取并解密 |
delete(id) | 删除记录 |
3.4 EncryptedCollection 查询
由于数据已加密,非 id 字段的查询需要特殊处理:
where(field, value)
↓ 非 id 字段?
→ 全量读取 → 逐条解密 → 内存过滤| 方法 | 说明 |
|---|---|
where('id', value) | 利用明文 id 索引,O(1) 直接命中 |
where(otherField, value) | 全量解密后内存过滤 |
filter(fn) | 自定义过滤函数,同样在解密后执行 |
limit(n) | 限制返回条数 |
sortBy(field) | 按字段排序 |
reverse() | 反转结果集 |
容错机制:批量解密时使用 Promise.allSettled,单条记录解密失败不影响其他记录的正常返回。
3.5 平台自适应存储工厂
/src/database/storage-store.ts 根据运行环境自动选择底层存储引擎:
| 平台 | 底层引擎 | 加密包装 |
|---|---|---|
| H5 (Web) | Dexie (IndexedDB) | EncryptedStore |
| App (iOS/Android) | SQLite (原生) | EncryptedStore |
function createStore<T extends { id: string }>(tableName: string): IStore<T> {
const inner = createInnerStore(tableName) // 平台选择
return new EncryptedStore<T>(inner) // 透明加密
}4. 密钥管理
4.1 密钥生命周期
设置密码 → PBKDF2 派生密钥 → 存入内存 (ActiveKey)
↓
用户解锁 → 输入密码 → PBKDF2 → 比较验证令牌 → 设置 ActiveKey
↓
自动锁定 / 手动锁定 → 清除内存 ActiveKey
↓
生物认证 → Keychain/Keystore 读取密码 → PBKDF2 → 恢复 ActiveKey4.2 内存密钥
/src/utils/crypto.ts — 活跃密钥仅存储于内存中:
| 操作 | 说明 |
|---|---|
setActiveKey(key) | 将派生密钥写入内存(模块级变量) |
getActiveKey() | 获取当前活跃密钥,不存在返回 null |
| 锁定/退出 | 内存释放,密钥从 RAM 中清除 |
密钥不写入磁盘、不序列化、不进入 Swap — 仅存在于进程内存中。
4.3 平台安全存储
生物认证场景下,加密密码需要安全持久化:
| 平台 | 安全存储 | API |
|---|---|---|
| iOS | Keychain Services | SecItemAdd / SecItemCopyMatching |
| Android | Android Keystore | KeyStore + KeyChain |
存储策略:kSecAttrAccessibleWhenUnlockedThisDeviceOnly — 仅在设备解锁后可访问,且不可迁移到其他设备。
4.4 加密配置持久化
加密配置通过 uni.storage + plus.storage 双写持久化(确保 iOS/Android 可靠),存储键 lifeos_enc_cfg:
| 字段 | 说明 |
|---|---|
salt | 32 字节随机 Salt(Base64) |
verifyToken | 验证令牌密文(明文为 LIFEOS_VERIFY_OK) |
verifyIV | 验证令牌 IV |
verifyAlgo | 验证令牌加密算法 |
verifyMAC | 验证令牌 HMAC(仅 CBC 模式) |
验证令牌的作用:无需解密真实数据即可验证密码正确性。输入密码 → 派生密钥 → 解密验证令牌 → 等于 LIFEOS_VERIFY_OK 则密码正确。
4.5 加密管理器
/src/utils/encryption-manager.ts — 加密配置的生命周期管理:
| 函数 | 说明 |
|---|---|
setupEncryption(password) | 首次设置密码,生成 Salt 和验证令牌 |
verifyEncPassword(password) | 验证密码(纯验证,不设 ActiveKey) |
unlock(password) | 验证密码并设置 ActiveKey |
lock() | 清除 ActiveKey |
changePassword(oldPwd, newPwd) | 修改密码(重新派生密钥,更新验证令牌) |
isEncryptionConfigured() | 检查是否已完成加密设置 |
5. 应用锁屏
/src/utils/lock-manager.ts — 独立于加密模块的锁屏管理器,提供应用级访问控制。
5.1 锁屏模式
| 模式 | 配置键 | 说明 |
|---|---|---|
enc | 加密密码 | 与数据加密使用同一密码,解锁同时恢复 ActiveKey |
pin | 独立 PIN 码 | 仅作锁屏验证,不解密数据(适用于数据未加密场景) |
PIN 码通过简单哈希存储(带固定 Salt lifeos_lock_salt_),生成 pin_xxx 格式的哈希值。注意:PIN 模式仅提供浅层访问控制,不参与数据加密。
5.2 自动锁定
支持 5 档自动锁定时间:
| 选项 | 时间 |
|---|---|
| 1 分钟 | 最高安全级别 |
| 5 分钟 | 高安全级别 |
| 15 分钟 | 推荐默认值 |
| 30 分钟 | 中等安全 |
| 60 分钟 | 较低安全(不建议) |
App 进入后台时立即触发倒计时,超时后自动清除内存密钥并锁定。
5.3 锁屏状态管理
/src/utils/lock-state.ts — 全局锁屏状态:
appLocked: Ref<boolean> // 当前是否锁定
appForeground: Ref<boolean> // App 是否在前台| 函数 | 说明 |
|---|---|
forceLock() | 强制锁定(清除密钥 + 导航到锁屏页) |
forceUnlock() | 解锁并重新加载所有 Store |
checkAndLock() | 加密已配置但无活跃密钥时自动锁定 |
6. 生物认证
/src/utils/biometric-manager.ts + /src/components/common/BioAuthOverlay.vue — 利用系统级生物认证提供便捷且安全的解锁体验。
6.1 认证流程
用户面对 BioAuthOverlay
↓
prompt 状态:自动触发系统生物认证
↓
┌─ 认证成功 → Keychain 读取密码 → PBKDF2 派生密钥 → 解锁
└─ 认证失败 → retry 状态:提供再次尝试 / 密码解锁选项
↓
密码解锁 → 自定义安全键盘输入 → 验证密码 → 解锁6.2 安全键盘
BioAuthOverlay.vue 使用自定义键盘而非系统键盘:
- QWERTY / 数字 / 符号三种布局切换
- 密码显示为
••••掩码,最长 64 字符 - 不唤起系统键盘,防止第三方键盘记录
- 错误时抖动动画提示
6.3 生物认证管理器
| 函数 | 说明 |
|---|---|
isBiometricAvailable() | 检测设备是否支持生物认证 |
enableBiometricUnlock(password) | 将密码写入 Keychain/Keystore |
disableBiometricUnlock() | 从 Keychain/Keystore 删除密码 |
authenticateWithBiometric() | 触发系统生物认证 |
存储标识:
| 字段 | 值 |
|---|---|
| Service | com.lifeos.biometric |
| Account | enc_password |
6.4 iOS 原生实现
BiometricAuth.swift — 使用 LAContext + .deviceOwnerAuthentication 策略:
- 支持 Face ID / Touch ID / 设备密码回退
- Keychain 操作通过
Security框架 - 提供
dumpKeychain()诊断接口
H5 和小程序环境 isBiometricAvailable() 始终返回 false。
7. 屏幕安全
/src/uni_modules/screen-security/ — 防止敏感信息通过截图或录屏泄露。
7.1 iOS 截图保护
ScreenSecurity.swift — 利用 UITextField.isSecureTextEntry 特性:
在 window layer 中插入隐藏的安全文本输入层
→ iOS 系统级截图/录屏保护自动生效
→ 多任务切换器中也无法截取应用内容这是 Apple 推荐的防止敏感内容被截图的方案,利用系统行为而非 Hook,稳定且合规。
8. 数据管理
/src/pages/data-management/index.vue — 提供完整的数据生命周期管理。
8.1 数据导出
导出备份
→ rawStore.toArray()(读取加密密文,不解密)
→ 打包为 JSON 文件
→ 分享 / 保存到本地备份文件包含加密密文,即使备份文件泄露,没有用户密码也无法解密。
8.2 数据导入
导入备份
→ 选择 JSON 备份文件
→ 覆盖写入所有 Store(需当前已有活跃密钥)导入时使用当前密钥重新加密,确保数据一致性。
8.3 密码遗忘恢复
针对"忘记密码但仍有备份"的场景,提供特殊的合并流程:
合并旧数据
→ 输入旧密码
→ 从旧备份中提取 Salt → PBKDF2 派生旧密钥
→ 解密旧备份数据
→ 用当前活跃密钥重新加密
→ 写入当前数据库
→ 自动清除旧备份文件这是唯一能恢复因忘记密码而无法访问数据的途径 —— 前提是用户之前做过导出备份。
8.4 数据清除
清除全部数据
→ 二次确认(危险操作警告)
→ 清空所有 Store
→ 可选择是否同时重置加密配置9. 隐私合规
9.1 隐私协议
/src/pages/privacy/agree.vue — 首次启动强制展示,不同意则无法使用:
- 同意 → 存储
privacy_agreed = '1',进入引导页 - 不同意 → 退出应用(
plus.runtime.quit()/window.close()) - 可随时点击查看完整隐私政策
9.2 隐私政策内容
/src/pages/privacy/index.vue — 完整隐私政策涵盖:
| 章节 | 内容 |
|---|---|
| 数据存储 | 本地为主,AES-256 加密,服务端无法解密 |
| 权限说明 | 存储、网络、位置、相机、麦克风、通知、振动、生物认证、蓝牙 |
| 第三方 SDK | 高德地图 SDK、百度语音 SDK、SQLite |
| 用户权利 | 查看权、导出权、删除权、撤回同意权 |
| 未成年人保护 | 不主动收集未成年人信息 |
| 政策更新 | 重大变更时应用内通知 |
9.3 权限最小化
| 权限 | 用途 | 按需申请 |
|---|---|---|
| 存储 | 数据持久化、备份导出 | 启动时 |
| 网络 | 云端同步、AI 对话 | 使用时 |
| 位置 | 轨迹记录 | 用户主动触发 |
| 相机 | 拍照记录 | 用户主动触发 |
| 麦克风 | 语音输入 | 用户主动触发 |
| 通知 | 任务/习惯提醒 | 设置时 |
| 生物认证 | 快速解锁 | 用户主动开启 |
所有敏感权限均采用按需申请策略,不预授权。
10. 启动安全流程
/src/App.vue — 应用启动时执行完整的安全检查链:
App 启动
↓
① privacy_agreed? ──→ 否 → 跳转隐私协议页(无法跳过)
↓ 是
② onboarding_complete? ──→ 否 → 跳转引导页
↓ 是
③ isEncryptionConfigured? ──→ 否 → 跳转加密设置页
↓ 是
④ isLockConfigured? ──→ 否 → 跳转锁屏设置页
↓ 是
⑤ getActiveKey()? ──→ 无 → 应用锁定,显示解锁弹窗
↓ 有
正常使用后台锁屏:App 进入后台(pause 事件)→ 自动清除活跃密钥 → 设置锁定状态。回到前台时需通过锁屏验证。
11. 安全威胁模型
11.1 防御的威胁
| 威胁 | 防御机制 |
|---|---|
| 设备丢失/被盗 | 锁屏密码 + 自动锁屏 + AES-256 加密 |
| 恶意应用读取数据 | 应用沙盒 + 数据库加密 |
| 网络传输窃听 | 端到端加密(服务端无法解密数据) |
| 截图/录屏泄露 | iOS 截图保护 |
| 暴力破解密码 | PBKDF2 10,000 次迭代(每次约 50ms) |
| 键盘记录 | 自定义安全键盘 |
| 备份文件泄露 | 备份为加密密文 |
11.2 不防御的威胁
| 威胁 | 原因 | 用户建议 |
|---|---|---|
| 设备已越狱/Root | 系统安全边界已被打破 | 避免在已越狱设备上使用 |
| 用户密码过于简单 | 加密强度取决于密码强度 | 使用 12 位以上混合密码 |
| 内存转储攻击 | 密钥在内存中明文存在 | 使用后及时锁定 |
| 社工/钓鱼 | 人为因素 | 不向任何人透露密码 |
11.3 安全边界
┌────────────────────────────────────────┐
│ 用户密码 │
│ (信任根) │
├────────────────────────────────────────┤
│ PBKDF2 密钥派生 │
├────────────────────────────────────────┤
│ 内存中的 ActiveKey │
│ ← 安全边界(进程内存) │
├────────────────────────────────────────┤
│ 透明加密存储 │
├────────────────────────────────────────┤
│ 平台安全硬件 │
│ iOS Keychain / Android Keystore │
└────────────────────────────────────────┘信任根是用户的密码。密码强度直接决定整体安全性。系统建议用户在加密设置页使用随机密码生成器(Star$Moon#1234 格式),并在安全的地方备份密码。