安全白皮书
版本 1.0 | 最后更新:2026 年 11 月 5 日
本文档说明 KeyMe Pass 如何加密你的数据:用了哪些算法、参数是多少、 密钥在哪里产生又存在哪里、我们能看到什么、以及哪些地方还没有做到。
这里写的都是代码里已经实现的,可以逐条拿去核对。实现改了,这份文档会跟着改。
一、设计原则
1.1 零知识
密码库在设备上加密完成之后才会离开设备。备份服务器上保存的是密文, 服务端没有任何代码路径可以解密用户数据。
这不是承诺,是架构决定的:解密所需的密钥由用户主密码在本机派生, 主密码从不上传。
1.2 数据去向由用户决定
密码库可以:
- 只存在本机,完全不联网
- 同步到 KeyMe Pass 托管的存储
- 同步到用户自己的 WebDAV 服务器
三种模式下客户端的加密行为完全一致(见 §4.3)。
1.3 为什么公开这些细节
遵循柯克霍夫原则:系统的安全性应当只依赖密钥,不依赖算法保密。
如果公开加密设计会让系统变得可攻击,那说明这个系统本来就不安全, 只是靠「没人知道」撑着。因此本文档公开到参数级。
但本文档不包含服务端部署结构、存储组织方式、密钥在磁盘上的确切路径 —— 那些属于攻击面侦察信息,与密码学设计无关,公开它们不增加任何可验证性。
二、密钥模型
2.1 总览
主密码
│
│ Argon2id (m=64MiB, t=3, p=4)
▼
vaultKEK ──┐
│ AES-256-GCM wrap
恢复密钥 │
│ HKDF ├──────────► DEK ──────► 加密密码库数据
▼ │ (32 字节)
recoveryKEK┤
│
PIN │
│ Argon2id
▼ │
pinKEK ────┤
│
生物识别 │
│ (取出随机密钥)
▼ │
bioKEK ────┘
核心事实:四个槽包装的是同一把 DEK。
DEK(Data Encryption Key,数据加密密钥)是实际用于加密密码库内容的密钥。 主密码、恢复密钥、PIN、生物识别四条路径,各自派生出不同的 KEK (Key Encryption Key,密钥加密密钥),但它们包装的都是同一把 DEK。
这带来一个重要性质:改主密码、用恢复密钥重置、轮换恢复密钥,都只更换 DEK 的包装,DEK 本身永不改变。 因此这些操作不需要重新加密整个密码库。
一台设备 = 一份本地数据 = 一把 DEK。DEK 只在切换账号(数据归并) 或重置清空时才会变更。
2.2 主密码 → vaultKEK
算法:Argon2id(RFC 9106),参数:
| 参数 | 值 |
|---|---|
| 内存(m) | 65536 KiB(64 MiB) |
| 迭代(t) | 3 |
| 并行度(p) | 4 |
| 输出长度 | 32 字节(256 bit) |
| 版本 | 0x13(19) |
| secret (K) / associatedData (AD) | 空 |
盐:16 字节,由 CSPRNG 生成(PointyCastle FortunaRandom, 种子取自操作系统安全随机源)。每个账号独立。
选择 Argon2id 而非 PBKDF2,是因为它同时抵抗 GPU 并行与侧信道攻击。 64 MiB 的内存开销使得大规模并行爆破的硬件成本显著上升。
跨平台一致性:六个平台使用三套实现(原生 C 库 / 鸿蒙 FFI / 纯 Dart), 三者引用同一组参数常量。写入密钥时执行字节级交叉校验 —— 用纯 Dart 实现复算一遍,与原生实现的结果做常量时间比对,不一致则 改用 Dart 结果落盘并永久禁用该平台的原生实现。
参数不可被远端篡改:密钥材料文件中虽然记录了 KDF 参数, 但客户端读取时只取盐值,参数恒用本地常量。 篡改服务端数据无法降低任何用户的 KDF 强度。
2.3 DEK
- 生成:32 字节,CSPRNG(同上)
- 包装算法:AES-256-GCM,IV 12 字节随机,认证标签 128 bit
- 明文 DEK 只存在于内存,锁定时原地清零
DEK 身份哨兵:首次生成 DEK 时,用它加密一段固定明文并保存密文。 解锁后用这个哨兵校验 DEK 是否与本机数据匹配,防止因异常状态 用错误的密钥覆写数据。
2.4 四个槽的 KEK 来源各不相同
| 槽 | KEK 来源 | 说明 |
|---|---|---|
| 主密码 | Argon2id(主密码, 账号盐) | 见 §2.2 |
| 恢复密钥 | HKDF-SHA256(RK, 盐, info=keyme:recovery_v1) | 不经 Argon2,见下 |
| PIN | Argon2id(PIN, 独立盐) | 参数同 §2.2 |
| 生物识别 | 32 字节随机值,存于平台密钥库 | 与生物特征无密码学关系;多数平台取用须经系统生物识别校验,见 §7 |
恢复密钥为什么不走 Argon2id:恢复密钥是系统生成的 125 bit 随机值 (见 §6),本身已有足够熵,不存在「弱口令」问题。慢哈希的作用是 拖慢对低熵口令的穷举,对高熵随机值没有意义。
主密码与 PIN 走 Argon2id,因为它们由用户选择,熵不可控。
2.5 槽位隔离
本节描述当前版本的实现。 早期版本的 PIN 槽与生物识别槽不具备本节描述的隔离, 存量设备会自动升级(见下方「存量与过渡期」)。
四个槽均使用按槽位区分的附加认证数据(AAD):DEK_WRAP:{master|pin|bio|recovery}:v2。 解包时用哪个 AAD 由「读的是哪个槽」决定,不取自数据本身;把一个槽的密文放进另一个槽, GCM 认证直接失败。
存量与过渡期(如实说明,避免被理解为「所有设备都已隔离」):
- V2 账号上,早期版本创建的 PIN / 生物识别槽使用统一 AAD(
DEK_WRAP), 在用户下一次用 PIN 或生物识别解锁成功时自动升级,无需额外操作;- 旧密钥架构(V1)账号的 PIN / 生物识别槽保持统一 AAD,升级到 V2 时重建;
- 因此读取侧在过渡期仍认统一 AAD。能写本机存储的攻击者可借此把槽回退为统一 AAD 格式 (解包仍需该槽自己的密钥),这条回落随 V1 退场一并移除。
利用上述回落需要攻击者具备本机文件写权限 —— 在该前提下存在更直接的攻击路径; 且四个槽的 KEK 来源各不相同(§2.4),即便 AAD 相同,A 槽的 KEK 也解不开 B 槽。 AAD 隔离是纵深防御的一层,不是唯一屏障。
三、本地数据加密
⚠️ 本节三条路径使用不同的算法,不可笼统概括。
3.1 结构化数据(密码、证件、验证码、笔记、SSH 密钥、支付卡、通行密钥)
- 存储:加密数据库(Hive 加密 box)
- 算法:AES-256-CBC + PKCS7 填充,IV 16 字节随机(每次写入新生成)
- 密钥:直接使用内存中的 DEK,不做二次派生
- DEK 不可用时拒绝打开加密库,不降级为明文
完整性说明:CBC 模式只提供保密性,不提供认证。 密文被篡改时解密不会报错,只会得到无意义数据。 数据完整性依赖操作系统的文件保护与应用沙箱。
这是所用嵌入式数据库的内置实现。在「攻击者能写入应用私有目录」 这一前提下,该攻击者通常已具备更直接的手段,因此我们接受这一边界, 但不将其描述为「带认证的加密」。
不加密的数据:账号基础配置、应用设置、订阅状态、同步墓碑记录。 这些需要在解锁前读取,且不含密码库内容。
3.2 附件与照片 —— 本地不加密
本机存储的附件文件(证件照、通用附件)以明文形式落盘。
保护依赖操作系统的应用沙箱与设备全盘加密,而非应用层加密。
如实披露的原因:这是真实现状。该设计源于内容寻址存储 (同一文件只存一份),文件地址由内容哈希决定。 改为加密存储需要同时重做去重机制,已登记为独立待办。
威胁模型上的位置:能读取应用私有目录的攻击者,在同一前提下 通常也能读取内存中的 DEK,因此附件加密在该场景下的增量收益有限。 但我们不以此为由把现状说成「已加密」。
附件上传到云端时是加密的,见 §4.2。
3.3 导入 / 导出 / 升级快照
使用 AES-256-GCM,IV 12 字节随机,AAD 为文件名或文件路径, 落盘格式 IV(12) || 认证标签(16) || 密文。
加密导出的密钥由用户设定的导出密码经 Argon2id 派生(盐每次新生成)。
四、同步与服务端
4.1 加密边界:按文件整体加密
同步目录下的对象:
| 对象 | 加密 |
|---|---|
| 各实体同步文件(密码 / 验证码 / 证件 / 支付卡 / 通行密钥 / 笔记 / SSH 密钥 / 附件索引 / 分类) | ✅ AES-256-GCM 整文件 |
| 兼容镜像文件(供旧版本客户端读取) | ✅ AES-256-GCM 整文件 |
| 附件文件 | ✅ AES-256-GCM 逐文件 |
| 根元数据文件 | ❌ 明文,见 §4.4 |
加密粒度是「每个同步文件整体」 —— 一个密码同步文件内的全部条目 共用一次加密,单个条目不是独立的密文单元。
密文信封(JSON,字段 Base64):
{ "version": 1, "dek_version": 2, "cipher": "...", "nonce": "<12 字节 IV>", "tag": "<16 字节认证标签>" }
4.2 附件同步
AES-256-GCM,IV 12 字节随机,格式 IV(12) || 认证标签(16) || 密文。 密钥为同一把 DEK。
4.3 托管同步与自有 WebDAV 的一致性
两种模式使用完全相同的加密路径。 客户端在读取同步配置之前 就已确定使用本机 DEK,因此文件格式、算法、密钥在两种模式下没有差异。
模式差异仅在于存储位置与认证方式。
📌 一个值得明说的边界:使用自有 WebDAV 时, 数据密文在第三方服务器,但密钥材料(主密码与恢复密钥包装的 DEK) 仍保存在 KeyMe Pass 服务端。 两者分离,任何一方单独都无法解密。
4.4 服务端与第三方能看到什么
我们认为如实披露元数据边界,比笼统宣称「服务端什么都看不到」更有价值。
✅ 可见(根元数据文件为明文)
| 内容 | 说明 |
|---|---|
| 启用了哪些实体类别的同步 | 文件名即类别 |
| 每类密文的字节大小 | 与条目数量粗略相关 |
| 同步版本号与最后更新时间 | |
| 提交设备的客户端标识符 | 随机 UUID,非硬件标识 |
| 附件标识符列表 | 见下方风险说明 |
❌ 不可见(全部位于密文内)
条目数量、字段名、标题、网址、用户名、密码、备注、标签名、文件夹名。
🚨 附件标识符的可探测性(必须写明的真实风险)
附件的远端对象名由明文内容的 SHA-256 前 16 位构成。
这意味着:持有某个特定文件的一方,可以通过计算其哈希, 确认某用户是否存有该文件 —— 虽然无法读取内容。
这是内容寻址存储的固有后果。改进方向是改用以 DEK 为密钥的 HMAC 作为地址,已登记为待办。
✅ 文件指纹不泄露内容:元数据中的指纹字段是以 DEK 为密钥的 HMAC-SHA256,不是明文哈希,因此无法用于已知明文比对。
4.5 服务端存储的内容
| 项 | 内容 | 能否解密用户数据 |
|---|---|---|
| 包装后的 DEK(两份:主密码 / 恢复密钥) | 密文 | ❌ 需主密码或恢复密钥 |
| KDF 盐 | 明文 | ❌ 盐不是秘密 |
| 登录令牌的 bcrypt 哈希 | 哈希 | ❌ |
| 恢复密钥验证值的 bcrypt 哈希 | 哈希 | ❌ |
| 账号邮箱、设备信息、存储用量 | 明文 | 不含密码库内容 |
对服务端代码的全量检索确认:不存在任何解密用户同步数据的代码路径。
五、认证
5.1 主密码不以任何形式上传
上传的是一个派生值:
loginToken = HKDF-SHA256(ikm = NFKC 规范化后的主密码, salt = 空, info = "keyme:login_v1", L = 32)
服务端存储 bcrypt(loginToken, cost=10),不存储 loginToken 本身。
loginToken 与 vaultKEK 是两条独立派生 —— 服务端即使持有 loginToken, 也无法得到 vaultKEK(后者经 Argon2id 与独立盐派生)。
5.2 这个设计的边界(如实说明)
HKDF 是单向函数,无法从 loginToken 反推主密码。
⚠️ 但 loginToken 的派生不使用 per-user 盐,info 字符串对所有用户相同。 因此它是主密码的确定性函数:相同主密码得到相同 loginToken。
后果:
- 数据库层面不可彩虹表攻击 ✅ —— 服务端存储的是带盐的 bcrypt
- 但若 loginToken 明文在某个环节泄露,对其做离线字典攻击的 单次成本仅为一次 HKDF-SHA256,远低于 Argon2id
准确的表述是:loginToken 不可逆推导,但弱口令仍受字典攻击约束。
密码库本身不受此影响 —— vaultKEK 走 Argon2id。
改进方向(引入 per-user 盐)已登记为待办,需要版本协商与迁移期。
5.3 防账号枚举
登录失败不区分「账号不存在」与「凭据错误」。 恢复流程中所有分支恒定执行相同次数的 bcrypt 比对, 以消除时序差异。
六、恢复密钥
6.1 生成
- 熵:125 bit(25 个 5-bit 索引,取自操作系统安全随机源)
- 字母表:Crockford Base32(去除易混淆的 I / L / O / U)
- 第 26 位为校验字符,用于减少抄录失误(非密码学校验)
- 展示为 4 字符一组
6.2 作用
恢复密钥派生出两个独立的值:
- 恢复 KEK(本机解开恢复槽):
HKDF-SHA256(RK, 盐, "keyme:recovery_v1") - 服务端验证值:
HKDF-SHA256(RK, 空盐, "keyme:rk_verifier_v1"), 服务端只存其 bcrypt 哈希
6.3 恢复流程的最小暴露设计
通过三因子验证(邮箱 + 邮箱验证码 + 恢复密钥验证值)后, 服务端只返回恢复密钥包装的那一份 DEK,绝不返回主密码包装的那份, 也不返回主密码的 KDF 盐。
原因:主密码分量加上盐,就是离线爆破主密码的完整材料。
两阶段提交:重置与轮换先写入暂存记录,正式数据不动; 用户确认已抄录新恢复密钥后才在一个事务内生效。 暂存期内旧密码与旧恢复密钥持续可用。
6.4 不可逆的后果
我们无法重置用户的主密码。 忘记主密码时,恢复密钥是唯一的找回途径。 两者都丢失,密码库数据将永久无法解密 —— 没有任何客服通道可以绕过这一点。
这是零知识架构的代价:我们能够替用户重置的密码, 别人也可能有办法重置。
七、平台密钥保管与生物识别
§7.1–§7.3 描述当前版本的实现。 早期版本在取用 bioKEK 时不经系统校验; 用户升级后,生物解锁会在下次开启时切到本节描述的模型。 仍未做到密码学绑定的平台(Android 弱档、Windows、Linux)如实列在 §7.2,不因「大部分平台已做到」而省略。
7.1 生物识别解锁的模型
生物识别不参与密钥派生。 bioKEK 是一把 32 字节随机密钥,与指纹 / 面容没有密码学关系, 它的作用是包装 DEK。区别在于谁守着 bioKEK:
系统生物识别校验 ──► 系统密钥库放行 bioKEK ──► 解开包装 ──► DEK
(认证与取用是同一次系统调用,校验由系统密钥库执行)
在 iOS / macOS、Android(强档设备)、HarmonyOS 上,bioKEK 存入系统密钥库时即声明 「必须通过生物识别才能取出」,由系统守门,应用代码无法绕过;没有通过当次系统认证,就拿不到 bioKEK。
7.2 两层保护,以及各平台做到了哪层
| iOS / macOS | Android 强档 | Android 弱档 | HarmonyOS | Windows | Linux | |
|---|---|---|---|---|---|---|
| bioKEK 存于系统密钥库,其他应用不可读 | ✅ | ✅ | ✅ | ✅ | 当前用户上下文 | 取决于发行版 |
| 取用时由系统强制校验生物识别 | ✅ | ✅ | ❌ 应用层判断 | ✅ | ❌ 应用层判断 | 无生物识别 |
| 生物识别信息变更后作废 | ✅ 增删指纹 / 重录面容即整把作废 | ✅ 新增生物特征即作废(仅删除不作废) | — | ⚠️ 按凭据类型,仅新增作废(见下) | — | — |
| 锁屏密码可代替生物识别 | ❌ | ❌ | ❌ | ❌ | ⚠️ 是(Windows Hello PIN) | — |
Android 为什么分档。 系统要求「密钥绑定认证」时,只接受强档(Class 3)生物识别; 多数国产机的人脸属于弱档(Class 2),无法用于这类密钥。我们按开启生物解锁时的设备能力定档: 设备有强档生物识别 → 走系统强制校验;只有弱档 → 密钥存在同一套系统密钥库中但不绑定认证, 取用前的生物识别由应用判断(即本表「取用时由系统强制校验」一列的 ❌)。由此产生一个已知代价:强档设备上, 系统弹窗只接受强档生物识别 —— 同时录有指纹与弱档人脸的用户,只能用指纹解锁。
HarmonyOS 的失效语义。 系统提供的最严设置(INVALID_NEW_BIO_ENROLL)按凭据类型生效: 录入新的生物特征后,通过该类生物特征无法再使用这把密钥,另一类不受影响 (例如新增指纹后指纹不可用、人脸仍可用);删除生物特征不使密钥作废;清除锁屏密码后整把密钥作废。 这比 iOS「任何变更即整把作废」宽,是该平台 API 内能取到的最严档。
密钥绑定的是开启生物解锁时系统里已录入的类型(系统不允许绑定未录入的类型): 开启时只录了指纹,之后再录入人脸,需关闭再开启生物解锁人脸才可用;解锁时只请求「已绑定且仍录入」的类型 (例如删掉人脸后,自动改用指纹)。系统里一种生物特征都没有了,生物解锁随之关闭。
失效之后。 密钥被系统作废时,应用识别出「已失效」,关闭生物解锁并提示用户重新开启, 不会自动重建(重建等于绕过「生物识别集合变了」这个信号)。主密码不受影响, 重新开启只需一次生物识别验证。从旧版本升级到 V2 密钥架构时,生物解锁同样会被关闭、需重新开启 —— 升级需要用新 DEK 重新包装,而 bioKEK 不再能被不经认证地读出。
扩展进程。 自动填充与通行密钥在系统扩展进程中运行,它们读取的是同一份受保护的 bioKEK (Apple 通过共享钥匙串访问组,Android 由同一应用进程内的原生代码),同样需要当次系统认证; 不存在供扩展使用的明文副本。
为什么 Windows / Linux 没有做到。 Windows 的 DPAPI 按「当前用户」解密,Windows Hello 只是取用前的界面门槛(且允许系统 PIN 通过);Linux 没有可用的生物识别接口。 若需要密码学绑定,Windows 需改用 NCrypt / Windows Hello 密钥凭据,属后续工作。
7.3 分平台说明
| 平台 | bioKEK 存储 | 访问控制 | 作废条件 |
|---|---|---|---|
| iOS / macOS | 钥匙串(共享访问组,macOS 为 data protection keychain) | .biometryCurrentSet + WhenPasscodeSetThisDeviceOnly:仅本机、需设有锁屏密码、不随备份迁移 | 增删指纹 / 重录面容;关闭锁屏密码 |
| Android 强档 | Android Keystore 密钥(AES-256-GCM)加密后存应用私有存储 | 每次使用均需强档生物识别(不接受锁屏密码),经 CryptoObject 由系统校验 | 新增生物特征;关闭锁屏 |
| Android 弱档 | 同上,密钥不绑定认证 | 应用层生物识别判断 | — |
| HarmonyOS | HUKS 密钥(AES-256-GCM,nonce 由 HUKS 生成)加密后存应用 Preferences | 解密需开启时已录入的人脸 / 指纹认证(不含锁屏 PIN),由 HUKS 校验认证令牌 | 新录入该类生物特征(按类型);清除锁屏密码 |
| Windows | DPAPI 加密文件 | 当前用户上下文;同一用户下任意进程可解 | — |
| Linux | Secret Service(libsecret) | 取决于发行版;无生物识别 | — |
结构化数据同步文件的加密不受上述差异影响 —— 那一层在所有平台上 都是 AES-256-GCM + 同一把 DEK。平台差异只影响「本机如何保管解锁材料」。
7.4 PIN 解锁
- PIN 为 6 位数字(约 20 bit 熵)
- 经 Argon2id(参数同 §2.2)派生 KEK,解开 PIN 槽
- 校验方式是「能否成功解开」,不存在独立的 PIN 校验值
- 无服务端参与
失败锁定阶梯(主应用内):30 秒 → 1 分 → 2 分 → 5 分 → 15 分 → 1 小时 → 3 小时 → 8 小时 → 24 小时,共 9 级。
设计细节:
- 冷却到期不自动降级,必须成功解锁一次才清零 —— 防止靠等待绕过
- 冷却期内再次失败,新冷却从原到期时间叠加
- 每升一级,允许的尝试次数递减(下限 2 次)
- 状态持久化,重启进程不重置
⚠️ 扩展进程中的 PIN 解锁不经过这套锁定机制。 在该路径下,穷举的唯一成本是 Argon2id 的计算代价 (每次约 64 MiB 内存 + 3 轮迭代)。
7.5 扩展进程的密钥缓存
自动填充与通行密钥在系统扩展进程中运行,不持有主密码, 通过三条路径之一获得 DEK:缓存命中 / 生物识别 / PIN。
| 平台 | 缓存介质 | 有效期 |
|---|---|---|
| Android | 仅内存,不落盘 | 15 分钟 |
| iOS / macOS | 明文存于共享 Keychain | 15 分钟 |
iOS / macOS 上缓存的 DEK 保护来自 Keychain 自身属性 (仅设备解锁态可读、不随备份迁移)与 App Group 访问控制。
单向约束:主应用与扩展进程之间的通道只允许写入与清除,不可回读 DEK。 否则用户在其他应用中触发填充解锁后,切回主应用会被静默解锁, 绕过主应用的锁定语义。
八、通行密钥(Passkey)
8.1 密钥生成
算法:ES256(ECDSA P-256 / secp256r1 + SHA-256), 签名采用 RFC 6979 确定性 k 并做 low-S 归一化。
私钥是软件生成的可导出密钥,不由硬件安全模块托管。
这是支持跨设备同步的必然结果 —— 硬件不可导出密钥与「在多台设备上 使用同一个通行密钥」互斥。
8.2 私钥保管
私钥从不以明文落盘:
- 加密:AES-256-GCM,12 字节 IV,128 bit 认证标签
- AAD 为凭据 ID —— 这使得私钥密文无法被移植到其他凭据上
- 与普通条目使用同一把 DEK,区别仅在 AAD
- 生成后立即将明文私钥字节清零
8.3 同步
当前架构下通行密钥随密码库同步,服务端只见密文。
8.4 用户验证状态如实上报
当依赖方(网站)要求 userVerification: "required" 时, 强制执行生物识别或 PIN 验证,不使用缓存的 DEK。
当依赖方未强制要求时,可在有效期内复用缓存, 此时认证数据中的用户验证标志位如实上报为「未验证」。
注册路径一律强制验证,不使用缓存。
早期版本曾将该标志位恒置为「已验证」—— 这等于向依赖方谎报验证状态, 已修复。我们在此记录这一点,因为一份只写优点的安全文档没有价值。
九、威胁模型
我们认为,明确说明「不防什么」与说明「防什么」同样重要。 一份只列能力的安全文档无法帮助用户做出判断。
9.1 我们防护的
| 威胁 | 防护方式 |
|---|---|
| 服务端被攻破 / 内部人员 | 服务端只持有密文与包装后的密钥,无解密路径 |
| 传输被监听 | 内容在设备上加密后才传输,TLS 之外的独立保护层 |
| 第三方 WebDAV 服务商窥探 | 同上;该服务商只能看到密文与 §4.4 的元数据 |
| 设备丢失(锁定状态) | 密码库加密落盘,DEK 不在磁盘上以明文存在 |
| 数据库泄露后的离线爆破 | Argon2id(64 MiB / 3 轮)显著抬高单次尝试成本 |
| 跨槽密文混用 | 主密码槽与恢复密钥槽的 AAD 隔离(PIN / 生物识别槽尚未覆盖,见 §2.5) |
| 远端篡改 KDF 参数 | 客户端只从远端读取盐值,参数恒用本地常量 |
| 账号枚举 | 登录不区分账号不存在与凭据错误;恢复流程恒时比对 |
| 恢复流程被用于爆破主密码 | 验证通过后只返回恢复密钥分量,不返回主密码分量与其盐 |
9.2 我们不防护的
以下场景超出本产品的保护能力。如实列出,而非回避。
| 场景 | 说明 |
|---|---|
| 设备已被 root / 越狱 | 攻击者可读取应用私有目录与进程内存。此前提下本地附件明文、已解锁时内存中的 DEK 均可获取。bioKEK:iOS / macOS、Android 强档、HarmonyOS 上取用须经系统生物识别校验,无法直接读出;Android 弱档、Windows、Linux 上可被读取(§7.2) |
| 恶意输入法 / 屏幕录制 / 键盘记录 | 主密码在输入时即可被截获 |
| 肩窥、摄像头拍摄 | |
| 物理胁迫 | 我们没有胁迫密码(duress password)机制 |
| 主密码本身是弱口令 | Argon2id 抬高成本但不能弥补低熵口令 |
| 操作系统或硬件层面的漏洞 | |
| 用户自行泄露恢复密钥 | |
| 本机附件文件的读取 | 附件在本地明文存储(§3.2),依赖系统沙箱 |
| 通过附件哈希探测文件持有情况 | 见 §4.4,可确认是否持有某特定文件,但无法读取内容 |
| 扩展进程中的 PIN 穷举 | 该路径无锁定阶梯(§7.4),成本仅为 KDF 计算 |
| 恶意的系统级填充服务 | 若用户将其设为系统默认填充提供方 |
| 本地数据库的定向篡改 | CBC 模式无认证(§3.1),可破坏但不易定向构造 |
| 供应链攻击 | 我们通过固定依赖版本降低风险,但不声称完全防护 |
9.3 边界说明
上述「不防护」项中的多数,前提都是攻击者已取得设备的高权限访问。 在该前提下,几乎所有本地密码管理器都无法提供有效保护 —— 因为解密能力必须在某个时刻存在于内存中。
我们列出它们,不是因为这些是本产品特有的弱点, 而是因为用户有权知道自己的保护边界在哪里。
十、已知限制
以下是我们尚未做到的事。在审查场景中,隐瞒的代价远大于说明。
10.1 产品闭源
源代码不公开。我们不把这说成优点 —— 它确实让外部审查更困难, 也意味着本文档的声称无法由第三方直接验证代码来确认。
10.2 尚无独立第三方安全审计
目前没有由独立机构出具并公开发布的审计报告。 这在计划中,但在完成之前我们不会声称已经做过。
10.3 本文档未覆盖的范围
- 浏览器扩展端的加密实现(本版本未纳入核查范围,因此不作声称)
- 对象存储层的服务端加密配置(我们的安全保证不依赖它)
十一、报告安全问题
发现安全漏洞请发送邮件至 support@keymepass.com, 标题注明「安全」或「Security」,我们会优先处理。
请在修复发布之前不要公开披露细节 —— 这是对正在使用产品的用户的保护。
我们承诺:
- 收到报告后尽快确认
- 修复后在版本说明中致谢(如报告者愿意具名)
- 不对善意的安全研究采取法律行动
附录:算法与参数汇总
| 用途 | 算法 | 参数 |
|---|---|---|
| 主密码 / PIN → KEK | Argon2id (RFC 9106) | m=64 MiB, t=3, p=4, 输出 32 B, v=0x13 |
| 恢复密钥 → KEK | HKDF-SHA256 | info=keyme:recovery_v1, 输出 32 B |
| 登录令牌派生 | HKDF-SHA256 | info=keyme:login_v1, 无盐, 输出 32 B |
| DEK 包装 | AES-256-GCM | IV 12 B, 标签 128 bit |
| 同步文件加密 | AES-256-GCM | IV 12 B, 标签 128 bit |
| 附件云端加密 | AES-256-GCM | IV 12 B, 标签 128 bit |
| 导入 / 导出加密 | AES-256-GCM | IV 12 B, 标签 128 bit, AAD=文件名 |
| 本地数据库 | AES-256-CBC + PKCS7 | IV 16 B,无认证标签 |
| 通行密钥签名 | ECDSA P-256 + SHA-256 | RFC 6979 确定性 k, low-S |
| 通行密钥私钥加密 | AES-256-GCM | IV 12 B, 标签 128 bit, AAD=凭据 ID |
| 服务端口令存储 | bcrypt | cost=10 |
| 随机数生成 | CSPRNG | PointyCastle Fortuna,种子取自系统安全随机源 |
*本文档随实现演进更新。参数或设计变更时,本文档必须在同一版本内同步修订。*