概览
zxcrypt 是一种块存储设备过滤驱动程序,可透明地加密写入块存储设备的数据,并解密从块存储设备读取的数据。zxcrypt 设备使用的底层块存储设备可以是几乎任何块存储设备,包括原始磁盘、ramdisk、GPT 分区、FVM 分区,甚至是其他 zxcrypt 设备。唯一的限制是块大小必须与页面对齐。 绑定后,zxcrypt 设备将在设备树中发布另一个块存储设备,供使用者正常交互。
用法
zxcrypt 现在是 FVM 组件的组成部分。
libzxcrypt.so 提供了四个用于管理 zxcrypt 设备的函数。 每个函数都接受一个或多个 zxcrypt_key_t 密钥,这些密钥将密钥数据、长度和槽位(在有多个密钥的情况下)相关联。
- zxcrypt_format 函数接受一个打开的块存储设备,并写入必要的加密元数据,使其成为 zxcrypt 设备。提供的 zxcrypt 密钥不会直接保护设备上的数据,而是用于保护数据密钥材料。
zx_status_t zxcrypt_format(int fd, const zxcrypt_key_t* key);
- zxcrypt_bind 函数指示驱动程序读取已加密的元数据并提取数据密钥材料,以用于透明地转换 I/O 数据。
zx_status_t zxcrypt_bind(int fd, const zxcrypt_key_t *key);
- zxcrypt_rekey 函数首先使用旧密钥读取已加密的元数据,然后使用新密钥将其写回。
zx_status_t zxcrypt_rekey(int fd, const zxcrypt_key_t* old_key, const zxcrypt_key_t* new_key);
- zxcrypt_shred 函数首先验证调用方是否可以使用提供的密钥读取加密的元数据来访问数据。如果成功,它会销毁包含数据密钥材料的加密元数据。 这样可以防止将来访问数据。
zx_status_t zxcrypt_shred(int fd, const zxcrypt_key_t* key);
技术详情
DDKTL 驱动程序
zxcrypt 是作为 DDKTL 设备驱动程序编写的。 src/lib/ddktl 是一个 C++ 框架 ,用于在 Fuchsia 中编写驱动程序。它允许作者使用模板化混入自动提供 src/lib/ddk函数指针和回调。
有两小部分功能无法在 DDKTL 和 C++ 中编写:
超级块格式
用于加密和解密数据的密钥材料称为数据密钥,存储在设备的保留部分(称为 superblock)中。此超级块的存在至关重要;如果没有它,就无法重新创建数据密钥并恢复设备上的数据。 因此,超级块会复制到设备上的多个位置以实现冗余。
zxcrypt 块存储设备使用者无法看到这些位置。每当 zxcrypt 驱动程序成功从一个位置读取并验证超级块时,它都会将其复制到所有其他超级块位置,以帮助“自我修复”任何损坏的超级块位置。
超级块格式如下,每个字段依次介绍:
+----------------+----------------+----+-----...-----+----...----+------...------+
| Type GUID | Instance GUID |Vers| Sealed Key | Reserved | HMAC |
| 16 bytes | 16 bytes | 4B | Key size | ... | Digest length |
+----------------+----------------+----+-----...-----+----...----+------...------+
- 类型 GUID:将此标识为 zxcrypt 设备。与 GPT兼容。
- Instance GUID:每个设备的标识符,用作 KDF salt,如下所述。
- 版本:用于指明要使用的加密算法。
- 密封密钥:数据密钥,由封装密钥加密,如下所述。
- Reserved:未使用的数据,用于将超级块与块边界对齐。
- HMAC:到目前为止(包括保留字段)的超级块的带密钥摘要。
封装密钥、封装 IV 和 HMAC 密钥均派生自 KDF。此 KDF 是RFC 5869 HKDF,它 将提供的密钥、实例 GUID 的“salt”和每个用途的标签(例如“wrap”或 “hmac”)相结合。KDF 不会 尝试执行任何速率限制。 KDF 可降低密钥重复使用的风险,因为新的随机实例 salt 会生成新的派生密钥。 HMAC
_注意:KDF 不会 执行任何 密钥拉伸。假设攻击者可以移除设备并自行尝试密钥派生,绕过 HMAC 检查和任何可能的速率限制。 为防止这种情况,zxcrypt 使用者应在派生其 zxcrypt 密钥时包含适当速率限制的 设备密钥,例如来自 TPM 的密钥。_
未来工作
在许多领域,可以、应该或必须做进一步的工作:
显示隐藏的绑定失败
目前,即使设备初始化失败,
zxcrypt_bind也可能会指示成功。运行绑定逻辑时,zxcrypt 不会 同步将设备添加到设备树。 它必须执行 I/O,并且不能阻止对device_bind的调用返回,因此它会生成一个初始化器线程并在完成后添加设备。截至 2017 年 10 月,这是 DDK 开发的一个活跃领域,政策正在更改为要求在返回之前添加设备,并且可能会在稍后进行额外的发布调用。 这样,可能需要让对
zxcrypt_bind的调用同步阻止调用方,直到设备准备就绪或明确绑定失败。使用 AEAD 而不是 AES-XTS
人们普遍认为,AEAD 通过在解密数据之前验证数据的完整性来提供卓越的加密保护。这是理想的,但需要额外的每块开销。 这意味着,使用者需要使用非页面对齐的块 (在移除内联开销后),或者 zxcrypt 需要将开销存储在行外并 处理 非原子写入失败。
支持多个密钥
为了方便密钥托管和/或恢复,可以轻松修改超级块 格式以包含一系列加密信封。考虑到这一点,libzxcrypt API 接受可变数量的密钥,但目前仅支持长度为 1,且唯一有效的槽位为 0。
调整工作器数量
目前有一个加密器和一个解密器。 这些加密器和解密器旨在与任意 数量的线程配合使用,因此可能需要进行性能调优,以找到最佳工作器数量,从而 平衡 I/O 带宽和 调度器抖动。
移除内部检查
目前,zxcrypt 代码会在内部边界检查许多错误情况,如果未满足这些情况,则会返回提供有用信息的错误。 为了提高性能,那些仅由程序员错误引起且不包含来自请求者或底层设备的数据的错误可以转换为在发布模式下跳过的“调试”断言。