安全白皮书

版本 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,见下
PINArgon2id(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 作用

恢复密钥派生出两个独立的值:

  1. 恢复 KEK(本机解开恢复槽):HKDF-SHA256(RK, 盐, "keyme:recovery_v1")
  2. 服务端验证值: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 / macOSAndroid 强档Android 弱档HarmonyOSWindowsLinux
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 弱档同上,密钥不绑定认证应用层生物识别判断—
HarmonyOSHUKS 密钥(AES-256-GCM,nonce 由 HUKS 生成)加密后存应用 Preferences解密需开启时已录入的人脸 / 指纹认证(不含锁屏 PIN),由 HUKS 校验认证令牌新录入该类生物特征(按类型);清除锁屏密码
WindowsDPAPI 加密文件当前用户上下文;同一用户下任意进程可解—
LinuxSecret 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明文存于共享 Keychain15 分钟

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 → KEKArgon2id (RFC 9106)m=64 MiB, t=3, p=4, 输出 32 B, v=0x13
恢复密钥 → KEKHKDF-SHA256info=keyme:recovery_v1, 输出 32 B
登录令牌派生HKDF-SHA256info=keyme:login_v1, 无盐, 输出 32 B
DEK 包装AES-256-GCMIV 12 B, 标签 128 bit
同步文件加密AES-256-GCMIV 12 B, 标签 128 bit
附件云端加密AES-256-GCMIV 12 B, 标签 128 bit
导入 / 导出加密AES-256-GCMIV 12 B, 标签 128 bit, AAD=文件名
本地数据库AES-256-CBC + PKCS7IV 16 B,无认证标签
通行密钥签名ECDSA P-256 + SHA-256RFC 6979 确定性 k, low-S
通行密钥私钥加密AES-256-GCMIV 12 B, 标签 128 bit, AAD=凭据 ID
服务端口令存储bcryptcost=10
随机数生成CSPRNGPointyCastle Fortuna,种子取自系统安全随机源

*本文档随实现演进更新。参数或设计变更时,本文档必须在同一版本内同步修订。*