Expand description
目录名的钥匙包裹边车(.omy-name)。
§为什么目录名要两层结构
原先目录名直接用 HKDF(KEK, info="omy/v1/dirname") 派生的密钥加密。
这个做法有一个致命的结构性限制:目录名只能被一把钥匙解开。
实测过它的后果(probe-tree-multikey):一棵树用两个密码加密,两个
密码都能打开每一个密文文件,但只有第一个能解开目录名,第二个解密整棵
树时报 content hash mismatch——用户看到的是「文件损坏」。恢复码撞的
是同一堵墙。
所以改成与文件一致的两层结构:
目录名密文 = AEAD(DK, 目录名) DK 是随机的目录密钥
边车 = 每把 KEK 各包一份 DK这样加一把钥匙只是多一份包裹,与目录名本身的长度无关。
§为什么包裹必须放边车文件,不能塞进目录名
算过了(probe-dirname-length),这不是取舍而是唯一可行解。
包裹塞进目录名时,以 32 字节的明文目录名为例:
| 钥匙数 | 磁盘名长度 | 上限 255 |
|---|---|---|
| 1 | 196 | 勉强 |
| 2 | 292 | 超 |
| 4 | 484 | 超 |
| 8 | 868 | 超 |
两把钥匙就破了 255(NTFS / ext4 / APFS 的共同下限)。更糟的是 8 把 钥匙时留给明文目录名的额度是 0 字节——连一个字都放不下。
包裹放边车则与钥匙数量完全无关:同样 32 字节的明文名恒定 100 字符, 明文目录名要超过 127 字节(约 42 个汉字)才触发截断。
§边车固定 8 槽、空槽填随机
与 crate::slot 同样的两条可否认性规则,理由也一样:边车长度固定,
看不出这个目录配了几把钥匙;空槽填随机而非填零,否则「哪些槽在用」
直接可见。
这里还多一层收益:边车现在恒定存在。原先它只在目录名超长时才出现, 那反而泄露了「这个目录的名字很长」。
§丢了边车会怎样
目录名再也解不开——这是必须在 UI 里说清楚的后果。它和目录是一体的, 不是缓存、不是可再生的索引。放在被加密的那个目录内部而不是父目录, 正是为了让它在目录被移动或复制时跟着走。
Structs§
- DirKey
- 随机生成的目录密钥。
Enums§
- Sidecar
Plan - 可管理模式下,一个边车槽位要怎么处理。
Constants§
- DK_LEN
- 目录密钥长度。
- KEYS_
SIDECAR - 钥匙包裹边车的文件名。
- SIDECAR_
FLAG_ MANAGED - 边车 flags 位:这棵树带槽位目录(可管理模式)。
- SIDECAR_
LEN - 边车文件总长:magic(8) + version(1) + flags(1) + reserved(2) + nonce(24)
- SIDECAR_
LEN_ MANAGED - 可管理模式下的边车总长:在基础长度之后追加加密的槽位目录。
- SIDECAR_
MAGIC - 边车魔数,用于快速排除「这不是我们的文件」。
- SIDECAR_
SLOTS - 固定槽位数,与文件的 Key Slot Area 保持一致。
- SIDECAR_
SLOT_ AREA - 槽位区总长。
- SIDECAR_
VERSION - 边车格式版本。
- WRAP_
LEN - 包裹后的长度:32 字节 DK + 16 字节 AEAD tag。
Functions§
- build_
sidecar - 构建边车内容。
- build_
sidecar_ managed - 构建带槽位目录的边车(可管理模式)。
- is_
managed - 这份边车是不是可管理模式。
- open_
sidecar - 从边车里解出目录密钥。
- open_
sidecar_ at - 同
open_sidecar,另外返回命中的槽位下标。 - read_
slot_ directory - 从可管理模式的边车里解出槽位目录。
- rebuild_
sidecar_ carrying - 在保留原有包裹的前提下,用新的钥匙集合重建边车。
- rewrite_
sidecar_ managed - 按计划精确改写边车的槽位(可管理模式)。