主密钥与 scrypt 派生 —— 为什么 Recovery Key 能安全找回
主密钥:每次解锁都在用的那把"真钥匙"
每个 Cryptomator 保险库有两把独立的 256 bit 密钥,合在一起 512 bit:
- Encryption Masterkey:加密所有文件内容和文件名
- MAC Masterkey:验证所有文件和文件名的完整性(防止被篡改)
这两把密钥才是真正的加密引擎。你的密码不直接用来加密文件——密码只用来派生一个 KEK(Key-Encryption Key,密钥加密密钥),然后用这个 KEK 把主密钥包裹起来(AES Key Wrap),存进 masterkey.cryptomator。所以:
- 改密码 = 改 KEK + 重新包裹主密钥 = 文件不需要重新加密(这也是为什么"弱密码升级强密码"如果云盘有版本历史就不推荐直接改,而是新建保险库搬数据)
- 主密钥本身始终不变 = Recovery Key 永远有效

CSPRNG:主密钥怎么生成的
Cryptomator 的主密钥由密码学安全伪随机数生成器(CSPRNG)生成:
- 桌面端(Java):
SecureRandom.getInstanceStrong(),用 SHA1PRNG 种子 440 bit - Cryptomator Hub(JavaScript):
crypto.subtle.generateKey()(Web Crypto API) - iOS 端(Swift):
SecRandomCopyByteswithkSecRandomDefault
三条路径都依赖各自平台的系统级 CSPRNG,不使用自建的伪随机算法。这是整个信任链的基础——密钥生成没有可预测性。
scrypt:为什么它在抗暴力破解
KEK 的派生用 scrypt(由 Colin Percival 设计的密码哈希算法),参数:
| 参数 | 值 | 含义 |
|---|---|---|
| cost | 32768 | 计算 scrypt 的迭代轮数(N=2^15) |
| blockSize | 8 | 内存占用量控制 |
| parallel | 1 | 并发度(Cryptomator 固定单线程,防止计时侧信道) |
| dkLen | 32 | 输出 KEK 的长度 = 32 bytes = 256 bit |
scrypt 的关键设计是同时吃 CPU 和内存。暴力破解一个 8 字符密码:
- 10 万轮 scrypt 在高端 GPU 上需要数十秒
- 暴力遍历所有可能的 8 字符密码需要数百年(实际算力取决于攻击者硬件)
如果你的密码是 4 个随机单词拼起来(Diceware),scrypt 派生后攻击者连一个都解不开。这也是官方推荐使用密码短语而不是短密码的原因。
AES Key Wrap:为什么这叫"包裹"不叫"加密"
主密钥不直接用 AES-256 加密——而是用 AES Key Wrap(RFC 3394,又称 AES KWP,Key Wrapping with Padding)。Key Wrap 专为"用一把密钥包裹另一把密钥"设计:
- 输入:一把 KEK(32 bytes)+ 一把待包裹的原始主密钥(64 bytes = 加密密钥 32 + MAC 密钥 32)
- 输出:两段 wrapped keys(各 32 bytes),分别写进 masterkey.cryptomator 的
primaryMasterKey和hmacMasterKey字段
Key Wrap 比传统的 "AES 加密 + HMAC" 更紧凑、更高效,而且它本身带 integrity tag,防篡改。
Recovery Key:主密钥的人类可读形式
Recovery Key 是一串12 词的人类可读英文单词序列(类似 BIP39)。它不是密码、也不依赖当前的保险库密码——它是加密主密钥的直接派生形式。这就是为什么:
- Recovery Key 可以重置当前密码(因为知道它就等于知道主密钥,可以换一个新密码重新派生 KEK 包裹主密钥)
- Recovery Key 独立于密码(改密码后旧 Recovery Key 依然有效)
- Recovery Key 不破坏加密(它只是人类可读的主密钥表示,攻击者拿到 Recovery Key 就等于拿到主密钥本体,可以解开所有文件)
把 Recovery Key 当密码一样保护。它可以完全绕过密码来访问你的保险库。
更详细的 Recovery Key 使用流程见 生成恢复密钥 和 丢失恢复与保险库抢救。