完整安全白皮书

想深入了解威胁模型、参数选型理由与所有工程实践细节?

ByteGuard 安全白皮书

版本:1.0 发布日期:2026 年 5 月 3 日 适用版本:ByteGuard iOS v2.3.x 及以上 联系:[email protected]


1. 引言

ByteGuard 是一款完全在用户 Apple 设备(iPhone / iPad / Mac)本地运行的密码管理器。本白皮书面向密码学审计方、独立安全研究者与高安全要求的用户,详述 ByteGuard 的密钥层级、加密算法、设备级保护机制以及威胁模型。

我们遵循 Kerckhoffs 原则:系统的安全性应仅依赖密钥保密,不依赖算法或架构保密。本文档公开的所有内容(算法、参数、密钥层级、存储位置)都不会削弱实际安全性;恰恰相反,公开是审计与信任的前提。


2. 信任模型与威胁假设

2.1 我们防御的威胁

威胁 防御方式
服务器入侵 / 数据泄露 ByteGuard 不运行任何后端服务器;同步完全走用户自己的 iCloud Private DB
设备丢失 字段级 AES-256-GCM 加密 + Keychain kSecAttrAccessibleWhenUnlockedThisDeviceOnly
网络中间人 主密码、Secret Key、明文 Vault DEK 永不离开设备 RAM;同步上传的是已 AES-256-GCM 加密的字段密文 + KEK 包裹后的 wrapped DEK(应用层加密,独立于 CloudKit 自身加密)
钓鱼登录 通行密钥(Passkey / FIDO2)私钥永不离设备
弱主密码暴力破解 Argon2id 64MB 内存成本 × 3 次迭代(OWASP 移动端推荐)
GPU 离线攻击 Argon2id 内存硬度对 GPU 极不友好
时序攻击(密码验证) 常量时间字节 XOR 比较
内存残留 Vault DEK 锁屏/挂起/切库时三次覆写(zero → random → zero);Master Key / KEK 仅作为解锁函数 local 变量短暂存在,由 Swift ARC 自动释放

2.2 我们不防御的威胁

我们对如下场景不做承诺——任何宣称能防御这些场景的密码管理器都在夸大其词:

  • 被入侵的 OS:操作系统本身被攻陷(越狱、内核漏洞、恶意 mdm)
  • 物理胁迫:用户被强迫主动输入主密码
  • 键盘记录:iOS 系统级木马记录用户输入
  • 屏幕截屏:解锁后第三方截屏类应用
  • 用户主动泄露主密码 + Secret Key

这些威胁不在本白皮书的安全模型内。


3. 密钥层级

3.1 总览

ByteGuard 采用五层密钥层级(每条目密钥隔离):

主密码 + Secret Key (128-bit) ──Argon2id──→ Master Key (32B)
                                                   │
                                                   ├─HKDF "auth-v1"──→ Auth Hash (验证用)
                                                   │
                                                   └─HKDF "kek-v1"──→ KEK (32B)
                                                                       │
                                                                       ↓ AES-GCM unwrap
                                                                  Vault DEK (32B, 全库唯一, 随机生成)
                                                                       │
                                                                       ↓ HKDF "type-{T}-item-{ID}-v1"
                                                                  Item Key (32B, 每条独立)
                                                                       │
                                                                       ↓ AES-256-GCM
                                                                  字段密文

3.2 输入

输入 长度 来源 持久化位置
主密码 用户输入 用户输入 永不持久化
Secret Key 128-bit (16B) libsodium randomBytes 创建库时生成 iOS Keychain(可选 iCloud Keychain 同步),同时以 BIP39 12 词形式展示给用户备份
Salt 256-bit (32B) libsodium randomBytes 创建库时生成 SwiftData(密码库元数据)

3.3 Master Key

派生算法:Argon2id(libsodium 实现)

输入:Argon2id(password ‖ secretKey.base64, salt) → 32 bytes

参数:

  • 内存成本:64 MB
  • 迭代次数:3
  • 并行度:libsodium 自动配置
  • 输出长度:32 字节

参数选型依据:OWASP Password Storage Cheat Sheet 移动端推荐。在 iPhone 14 Pro 上典型耗时 100~250 ms,即对用户无感、对 GPU 攻击者极昂贵。

安全特性:

  • Master Key 永不离开设备,永不持久化
  • 退出 / 锁屏后立即清零
  • 服务器对此密钥无任何知识

3.4 Auth Hash 与 KEK

两者都从 Master Key 通过 HKDF-SHA256 派生(RFC 5869):

派生密钥 用途 HKDF info 字符串 输出长度
Auth Hash 验证主密码是否正确 "vault-auth-v1" 32B
KEK 解封 Vault DEK "vault-kek-v1" 32B

职责严格分离:

  • Auth Hash 不参与任何数据解密,仅用于密码验证
  • KEK 不直接接触用户数据,仅用于解封 DEK

密码验证使用 常量时间 XOR 比较,杜绝时序攻击。

3.5 Vault DEK(数据加密密钥,全库唯一)

生成方式:库创建时由 libsodium randomBytes 生成 32 字节随机密钥;用 KEK 通过 AES-256-GCM 加密(即 Wrapped DEK)后存储到 SwiftData。

为什么 DEK 全库唯一而不是 per-item 随机? 持久化只存 1 个 wrapped DEK(不用 N 份),存储与同步开销随条目数线性下降但不暴涨。每条目密钥隔离由下一层 Item Key 完成。

修改主密码的关键性质:

  1. 重新派生 Master Key / Auth Hash / KEK
  2. DEK 不变,仅用新 KEK 重新 wrap DEK
  3. 全库无需重加密——这是 envelope encryption 的核心优势

3.6 Item Key(条目专用密钥)

派生算法:HKDF-SHA256(Vault DEK, info="type-{T}-item-{ID}-v1") → 32B

  • T:数据类型(login / card / note / passkey / identity 等)
  • ID:条目唯一 ID

安全性:

  • 每条记录使用独立密钥,单个 Item Key 泄露不影响其他条目
  • 派生确定性 → 无需为每条目持久化密钥
  • 类型 + ID 拼接 → 即使两条记录 ID 相同(不同类型)也会派生不同 Item Key

3.7 字段密文

算法:AES-256-GCM(CryptoKit 实现)

对每个敏感字段单独加密:用户名、密码、URL、自定义字段、卡号、CVV(仅会话内存中存在)、备注、附件等。

唯一 IV:CryptoKit 自动为每次加密生成唯一 96-bit IV,相同明文永不产生相同密文。

禁用持久化的字段:

  • CVV / CVC(PCI DSS 红线,全程不持久化)
  • 主密码(永不持久化)
  • 完整的生物识别原始数据(由 iOS 处理,应用永不接触)

4. 设备级保护

4.1 iOS Keychain ACL

为支持 Face ID / Touch ID 快速解锁,Vault DEK 的副本会在用户启用生物识别时存入 iOS Keychain:

访问控制 (ACL):
  kSecAttrAccessible       = kSecAttrAccessibleWhenUnlockedThisDeviceOnly
  kSecAccessControlFlags   = .biometryCurrentSet

含义:

  • WhenUnlockedThisDeviceOnly:设备未解锁时不可读;不参与 iCloud Keychain 同步
  • biometryCurrentSet:必须通过 Face ID / Touch ID 验证;当用户在系统设置中修改/重录生物识别数据时,iOS 自动删除该条目

4.2 Secure Enclave 透明保护

iOS Keychain 条目由 Secure Enclave 内的 device key 加密。当应用调用 SecItemCopyMatching 时:

  1. iOS 触发 Face ID / Touch ID 提示
  2. 用户验证通过后,SE 用 device key 解密 Keychain 条目
  3. 返回明文 Vault DEK 给应用

关键事实:

  • 应用层不持有任何 BiometricKey / SE 密钥
  • 信任边界完全交给 iOS Keychain ACL
  • SE 操作完全由 iOS 系统隔离层完成,应用不可触

4.3 Face ID 重录的自动失效

iOS 的 biometryCurrentSet 语义保证:用户重新录入 Face ID(哪怕只是补一个面孔),iOS 自动删除该 Keychain 条目。

ByteGuard 启动时通过 LAContext.evaluatedPolicyDomainState 检测此变化:

  • 检测到生物识别域变更 → 自动禁用快速解锁
  • 清除内存中所有缓存的 DEK 与解密数据
  • 强制要求用户重新输入主密码

4.4 严格模式(拒绝降级)

如果 biometryCurrentSet 不可用(模拟器、未注册生物识别),DEK 缓存直接抛错,不降级到无生物保护的普通 Keychain 存储。

设计理由:

  • 用户启用 Face ID 的意图是"强化保护",偷偷降级到弱存储违背心智模型
  • Fallback 反而比"未启用 Face ID"更弱——未启用时根本不缓存,fallback 反而缓存了
  • 调用方收到异常仅记日志,不中断主流程:下次解锁退回输主密码即可

5. 同步与本地存储

5.1 SwiftData + 字段加密

所有用户数据存储在 iOS SwiftData 数据库。ByteGuard 不依赖 SwiftData 的存储加密——即使 SwiftData 文件被攻击者拿到原始字节,所有敏感字段都已是 AES-256-GCM 密文。

5.2 iCloud 同步(可选)

iCloud 同步默认关闭,由用户在设置中显式启用。启用后通过 SwiftData ModelConfiguration(cloudKitDatabase: .private(...)) 接入 CloudKit Private Database。

关于 CloudKit 加密的事实陈述(避免常见误解):

  • CloudKit Private DB 默认情况下由 Apple 服务器存储,传输 HTTPS、静态加密,但解密密钥由 Apple 持有
  • 只有用户额外开启 iCloud "高级数据保护"(Advanced Data Protection,iOS 16.2+)时,CloudKit Private DB 才进入端到端加密模式
  • 因此 ByteGuard 不假设 Apple 不可读 CloudKit,而是自己再加一层 AES-256-GCM 字段加密

关键性质:

  • iCloud 上传的是 ByteGuard 已加密的字段密文(密文 + 96-bit IV + 128-bit GCM tag)
  • 即使 Apple 配合司法请求或 CloudKit 服务被入侵,看到的也只是 AES 密文,没有 Vault DEK 无法还原
  • Vault DEK 的 wrapped 副本通过 iCloud Keychain 同步(这是 Apple 自己的端到端加密产品,无论是否启用 ADP 都是 E2EE,Apple 不持有解密密钥)
  • 跨设备恢复需要:iCloud 账户 + Secret Key + 主密码三者齐全

5.3 常见疑问:wrapped DEK 上传 CloudKit 真的安全吗?

看到「Vault DEK 的 wrapped 副本会上传 iCloud」可能让人本能担心:把"密钥"放云上不是反直觉吗?这一节正面回答这个疑问。

一、为什么必须上传 wrapped DEK?

跨设备同步在密码学上要求:用户在新 iPhone / iPad 首次登录后,必须能解密所有已存储的字段。如果 wrapped DEK 不同步,新设备永远拿不到 DEK,所有密文等同于丢失。

Envelope encryption(KEK 包裹 DEK)就是为了解决这个矛盾的标准方案 —— 把"用户主密码"与"数据加密密钥"解耦,前者只在用户脑里,后者用前者派生的 KEK 包装后可以放任何地方(包括公开服务器)。

二、攻击者拿到 wrapped DEK 之后要做什么?

要从 wrapped DEK 还原明文 DEK,攻击者必须依次突破:

wrapped DEK = AES-256-GCM(KEK, plaintext_DEK, IV)
                ↑
          需要 KEK 才能解密
                ↑
          KEK = HKDF-SHA256(Master Key, "vault-kek-v1")
                ↑
          需要 Master Key
                ↑
          Master Key = Argon2id(主密码 ‖ Secret Key, salt) · 64MB × 3 iter
                ↑
          三重壁垒同时具备:
          ① 用户的主密码(攻击者不知道)
          ② 128-bit Secret Key(用户专属,Apple 也拿不到,从未离开设备明文形态)
          ③ 每次猜测要做 64MB × 3 iter 的 Argon2id 计算

三、实际攻击成本量化

假设主密码强度只有 60 bits(一个像样但不极端的密码),加上 128-bit Secret Key 的熵 = 188 bits。

  • 每次猜测要做 64MB 内存的 Argon2id 计算 — GPU 帮不上忙(内存硬度设计)
  • 假设全人类 GPU 加在一起 ≈ 10^10 次 Argon2id-64MB / 秒(极乐观估计)
  • 暴力遍历 2^188 个组合需要 ~10^46 年

即使量子计算成熟、Argon2id 被加速 10^15 倍,仍需 10^31 年 —— 宇宙寿命的 10^21 倍。

四、对比行业标杆

所有主流密码管理器都把 wrapped 主密钥(或等价物)放云端:

厂商 同步方式 wrapped 主密钥位置 KDF
1Password 自有云 上传 PBKDF2 / 受 Account Password + Secret Key 保护
Bitwarden 自有云 上传 PBKDF2 / Argon2id(用户可选)
Dashlane 自有云 上传 Argon2id
Apple Keychain iCloud Keychain E2EE 上传(Apple ADP 加密层) Apple 内部
ByteGuard iCloud Private DB 上传 Argon2id 64MB × 3 + 128-bit Secret Key

ByteGuard 的密码学强度处于行业第一梯队,关键差异:

  • Secret Key 128-bit 熵作为第二因素(与 1Password 同款思路)—— 即使主密码弱、即使 Apple 配合司法、攻击者拿到 wrapped DEK 也无从下手
  • Argon2id 64MB 移动端推荐参数(与 Dashlane 同级)

五、结论

Wrapped DEK 上 iCloud 不仅不削弱安全,反而是 envelope encryption 模式的核心安全前提。

安全等级完全依赖于:① 主密码强度 ② Secret Key 永不泄露 ③ Argon2id 参数。CloudKit 是否被入侵、Apple 是否能解密 CloudKit、网络中间人是否截获密文 —— 在这套设计下都无关紧要。


5.4 AutoFill Extension(v2 Derived Key 隔离)

iOS AutoFill 是独立的进程上下文,无法直接访问主 App 的内存中 DEK。ByteGuard v2 方案:

  • 主 App 启用 AutoFill 时,从 Vault DEK 派生一个独立的 Derived AutoFill Key(HKDF + vault id)
  • 该 Key 用 biometryCurrentSet 保护写入 App Group 共享 Keychain
  • AutoFill Extension 可在 Face ID 通过后读取此 Key,仅解密用户当前需要填充的那一条凭据

好处:AutoFill 不接触 Vault DEK 主密钥,最小权限原则。


6. 安全工程实践

6.1 内存清零

SecureMemory.zero(&buffer) 对敏感字节执行三次覆写 + 内存屏障:

  1. 全部置 0(memset)
  2. 全部置随机字节(arc4random_buf)
  3. 全部置 0(memset)
  4. 每一步后 load(as: UInt8) 阻止编译器优化掉清零

清零范围(精确事实):

  • ✅ Vault DEK(_currentDEK 实例变量):在 lockVault / clearAllKeychainData 时显式调用 SecureMemory.zero 三次覆写
  • ⚠️ Master Key / KEK:作为 unlockVault / performUnlockOperations 函数内的 local 变量短暂存在,函数返回后由 Swift ARC 自动释放。ARC 释放只保证引用计数归零,不保证字节立即被覆写 — 字节会停留在 heap 直到被新分配覆盖。这是当前实现的限制。
  • ⚠️ Argon2 内部缓冲区:由 libsodium 内部管理,libsodium 在派生完成后会调用 sodium_memzero 自行清理

防御范围:编译器优化、heap dump(越狱/调试器场景)、应用挂起后内存被换页到磁盘。不防御:Master Key / KEK 在 ARC 释放之前的短暂窗口(毫秒~秒级)内被实时内存读取的攻击。

6.2 常量时间比较

密码验证使用按字节 XOR 累加:

var result: UInt8 = 0
for i in 0..<computedHash.count {
    result |= computedHash[i] ^ storedHash[i]
}
guard result == 0 else { throw .invalidPassword }

确保比较耗时与匹配位置/数量无关,杜绝时序侧信道。

6.3 CSPRNG 来源

所有随机数(Salt、Vault DEK、Secret Key、IV)来源:

  • libsodium randomBytes.buf()(底层调用 macOS / iOS 的 arc4random_buf,最终来自内核 CSPRNG)
  • CryptoKit 的 AES.GCM.seal() 内部 IV 由 CryptoKit 自动生成

绝不使用 Math.random()、Date()、PID 或其他低熵来源。

6.4 密码生成器

应用内置的密码生成器:

  • 随机模式:SecRandomCopyBytes(内核 CSPRNG)
  • 助记短语:EFF Long Word List(7776 词)
  • 数字 PIN:CSPRNG 抽样

6.5 自动化测试

  • ViewModel / Manager / Crypto Service 均有单元测试
  • Crypto 测试覆盖:派生正确性、wrap/unwrap 往返、改密码后旧密文仍可解(用新 KEK 解新 wrapped DEK)
  • UI 测试覆盖:解锁流程、生物识别失效检测

7. 我们做不到的事

零知识架构意味着以下场景我们也做不到——这是设计目标,不是限制:

场景 状态
重置主密码 ❌ 不能 — 没有第二条解密路径
找回丢失的 Secret Key ❌ 不能 — 我们从未持有任何 Secret Key 数据
配合执法机构解密用户数据 ❌ 不能 — 服务器无密钥、无密文
在加密中预留后门 ❌ 不能 — 算法与参数全部公开,可独立审计
收集用户密码用于风控 / 训练 ❌ 不会,且技术上做不到
推送广告或第三方 SDK ❌ 应用内不接入任何分析、广告、崩溃上报 SDK

用户同时丢失主密码和 Secret Key → 数据永久无法解密。这是设计强约束,不是产品缺陷。


8. 算法与版本

8.1 当前算法选型

用途 算法 实现 标准
主密码 KDF Argon2id 64MB / 3 iter libsodium RFC 9106
密钥派生 HKDF-SHA256 CryptoKit RFC 5869
对称加密 AES-256-GCM CryptoKit NIST SP 800-38D
密码强度评估 zxcvbn 移植 内部 —
泄露检测 k-anonymity HIBP API 内部 HIBP API v3
助记词 BIP39 12 词(128-bit 熵) 自实现 BIP-0039
通行密钥 WebAuthn / FIDO2 ECDSA P-256 iOS AuthenticationServices FIDO2 / CTAP 2.2

8.2 版本与兼容

所有 HKDF info 字符串带 -v1 后缀,便于未来无缝迁移到 v2 算法(例如 SHA-3、AES-256-GCM-SIV)。每个数据库迁移路径都会保留旧密文的解密能力。


9. 已知限制与未来工作

已知限制

  1. AutoFill v1 用户:未升级到 v2 Derived Key 的老用户仍在使用 legacy biometric key 路径。新安装均默认 v2。
  2. iCloud Keychain 失效:用户登出 iCloud 账户后 Secret Key 在新设备上需手动恢复(输入 12 词助记词)
  3. 密码历史:保留旧密码副本(用户可控开关),副本同样字段加密;删除条目时一并清除

未来计划

  • 端到端加密的可选「家庭共享」(Diffie-Hellman 密钥协商,主密钥永不外传)
  • Argon2id 参数动态自适应(设备 RAM 检测)
  • WebAuthn 跨设备同步(CTAP 2.2 hybrid transport)

附录 A:参数总表

参数 值
Argon2id 内存 64 MB
Argon2id 迭代 3
Argon2id 输出 32 bytes
Salt 长度 32 bytes
Secret Key 长度 16 bytes (128-bit)
Master Key 长度 32 bytes (256-bit)
KEK 长度 32 bytes (256-bit)
Vault DEK 长度 32 bytes (256-bit)
Item Key 长度 32 bytes (256-bit)
AES-GCM IV 长度 12 bytes (96-bit)
AES-GCM Tag 长度 16 bytes (128-bit)
Auth Hash 长度 32 bytes
BIP39 助记词 12 词

附录 B:HKDF info 字符串清单

info 用途
vault-auth-v1 Auth Hash 派生
vault-kek-v1 KEK 派生
type-{T}-item-{ID}-v1 Item Key 派生(按数据类型 + 条目 ID)
vault-dek-v1 (已弃用)旧版 DEK 派生

附录 C:参考资料

  • RFC 9106 — Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications
  • RFC 5869 — HMAC-based Extract-and-Expand Key Derivation Function (HKDF)
  • NIST SP 800-38D — Recommendation for Block Cipher Modes of Operation: Galois/Counter Mode (GCM)
  • BIP-0039 — Mnemonic code for generating deterministic keys
  • OWASP Password Storage Cheat Sheet
  • Apple Platform Security Guide — Keychain Data Protection / Secure Enclave
  • Have I Been Pwned API v3 — k-anonymity password range query

版本历史

版本 日期 变更
1.0 2026-05-03 白皮书首版

反馈与漏洞披露

发现安全问题请邮件至 [email protected]。我们承诺:

  • 24 小时内首次回复
  • 与报告者协调披露窗口
  • 重大漏洞修复后致谢(征得同意时具名)

Download .md (English) · 下载 .md(中文)