概述
Zxcrypt 是一个块设备过滤器驱动,它加密写入块设备的数据和解密从块设备读取的数据。Zxcrypt 设备使用的底层块设备可以是几乎任何块设备,包括原始磁盘、内存磁盘、GPT 分区、FVM 分区甚至其他 zxcrypt 设备。它唯一的限制是块大小必须与页面对齐。绑定后,zxcrypt 设备将在设备树中发布另一个可供消费者正常交互的块设备。
用法
Zxcrypt 包含由 libzxcrypt 提供的驱动程序和库。
管理 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 设备编写的驱动程序。DDKTL 是 Fuchsia 用来写驱动程序的一个 C++ 框架。
它允许开发者使用模板化混合来自动提供DDK函数指针和回调。
在 zxcrypt 中,设备是“可传递消息的”、“可查询的”、“可获取大小的”、“不建议使用的绑定”,并实现了DDKTL 的BlockProtocol中列出的方法
下面两个小功能不能用 DDKTL 和 C++ 编写:
工作线程
设备启动工作线程时,为所有 I/O 请求创建管道。
每个 I/O 都有其操作的 I/O 类型、它将等待的传入请求 I/O 队列和数据密码。
当接收到请求时,如果操作码与它正在查找的操作码匹配,它将在传递请求之前使用其密码转换请求中的数据。
整个管道如图所示:
DdkIotxnQueue -+
\ Worker 1: Underlying Worker 2: Original
BlockRead ---+---> Encrypter ---> Block ---> Decrypter ---> Completion
/ Acts on writes Device Acts on reads Callback
BlockWrite -+
“加密器”在将数据发送到底层块设备之前对每个I/O写请求中的数据进行加密,而“解密器”则对来自底层块设备的每个I/O读响应中的数据进行解密。
cipher 的密钥长度必须至少为16个字节,确保语义安全的( IND-CCA2 ),并将块偏移量合并为“ tweak ”,Fuchsia 目前正在使用 AES256-XTS 。
Rings and Txns
为了保持数据在加密和解密对原始的 I/O 请求者透明,工作线程必须先复制数据然后再传输。I/O 请求通过管道发送不是真正的原始请求,而是封装原始请求的“影子”请求。
当需要“影子”请求时,它们由 VMO 中的页面按顺序进行分配
当工作线程需要传输数据时,它会将原始数据进行加密,并封装写请求到影子请求,或者将影子请求中的数据解密为原始数据,并封装读请求。
一旦可以将原始请求交还给原始请求者,影子线程将取消分配及其页面 decommitted 。这可确保使用的内存不会超过未完成I/O请求所需的内存。
超级块的格式化
用于加密和解密数据的密钥被称为数据密钥,并且被存储在设备的被称为“超级块”的保留部分中。超级块的存在时必要的;如果没有它,就不可能重新创建数据密钥并恢复设备上的数据。
因此,超级块被复制到设备上的多个位置以实现备份。这些备份的位置是对 zxcrypt 块设备消费者不可见的。每当 zxcrypt 驱动程序成功地从一个位置读取并验证超级块时,它就会将其复制到所有其他超级块位置,以帮助“自我修复”任何损坏的超级块位置。
超块格式如下,每个字段依次描述:
+----------------+----------------+----+-----...-----+----...----+------...------+
| Type GUID | Instance GUID |Vers| Sealed Key | Reserved | HMAC |
| 16 bytes | 16 bytes | 4B | Key size | ... | Digest length |
+----------------+----------------+----+-----...-----+----...----+------...------+
- Type GUID: 标识这个是一个 zxcrypt 设备。 兼容 GPT 。
- Instance GUID: 设备的标识符, 用作 KDF,如下所述。
- Version: 用于指示要使用的加密算法。
- Sealed Key: 由如下所述的包络密钥加密的数据密钥。
- Reserved: 保留数据用于超级块与块边界对齐。
WRAP 密钥、WRAP IV 和 HMAC 密钥都派生自 KDF。
KDF 是一个RFC 5869 HKDF,它结合了所提供的密钥、实例 GUID 的“撒盐(salt)加密”和每次使用的标签(如“WRAP”或“HMAC”)。
KDF 不 会尝试进行任何速率限制。KDF 降低了密钥重用的风险,因为新的随机实例撒盐加密将导致新的派生密钥。HMAC 可防止意外或恶意修改未被检测到,而不会泄露有关 zxcrypt 密钥的任何有用信息。
注:KDF 不 做任何按键延展。假设攻击者可以移除设备并自行尝试密钥派生,从而绕过HMAC检查和任何可能的速率限制。
为了防止这个,Xcrypt 用户应包括适当的速率限制设备密钥,如,那些来自 TPM 的密钥,在导出它们的 zxcrypt 密钥时。
发展趋势
有几个方面可以、应该或必须做进一步的工作:
-
表面隐藏绑定失败
目前,即使设备未能初始化,zxcrypt_bind
也可能指示成功。当绑定逻辑运行时,zxcrypt 不 同步地将设备添加到设备树中。它必须执行I/O,并且不能阻止对device_bind
的调用返回,因此它会产生一个初始化器线程,并在完成时添加设备。截至2017年10月,这是 DDK 开发的一个活跃领域,政策正在改变,要求在返回之前添加设备,之后可能还会调用发布。这样,可能希望对
zxcrypt_bind
的调用为调用者同步阻塞,直到设备准备就绪或绑定明确失败。
-
使用 AEAD 而不是 AES-XTS
人们普遍认为,AEAD 在解密数据之前验证其数据的完整性,从而提供卓越的密码保护。这虽然很好,但需要额外的每个数据块开销。这意味着要么消费者需要使用非页面对齐的块(一旦消除了内联开销),要么 zxcrypt 将需要将开销存储在内联外并处理非原子性写入失败。
-
支持多密钥
将超块格式直接修改为具有一系列加密信封,为密钥托管和/或恢复提供便利,考虑到这一点,libzxcrypt API采用数量可变的密钥,尽管当前支持的唯一长度是1,唯一有效的槽是0。
-
调整工作线程数量
目前有一个加密器和一个解密器。这些线程被设计为使用任意数量的线程,因此可能需要进行性能调优,以找到平衡 I/O 带宽和周期程序变动的最佳工作线程数量。
-
移除内部检查
目前,zxcrypt 代码在内部边界检查许多错误条件,如果不满足这些条件,则返回信息性错误。为了提高性能,可以将仅由程序员错误引起的断言(而不是来自请求者或底层设备的数据)转换为“调试”断言,并在发布模式中跳过这些断言。
- 中文文档地址:https://fuchsia-china.com/docs/zh-hans/concepts/filesystems/zxcrypt/
- GitHub 链接:https://github.com/FuchsiaOS/FuchsiaOS-docs-zh_CN/blob/2021/concepts/filesystems/zxcrypt.md
- 原始英文文档:https://fuchsia.googlesource.com/fuchsia/+/master/docs/concepts/filesystems/zxcrypt.md
- 翻译者:logincat 校稿者:Dongchan 排版者:N0B8D1
Comments