概览
zxcrypt 是一个块设备过滤器驱动程序,可对写入数据的数据和 从块存储设备的数据中读取数据。zxcrypt 设备的底层块存储设备 用途几乎可以是所有块设备,包括原始磁盘、ramdisk、GPT 分区、FVM 分区 甚至其他 zxcrypt 设备唯一的限制是块大小必须与页面对齐。一次 绑定后,zxcrypt 设备将在设备树中发布另一个块存储设备,消费者可以 哪些对象可以正常互动。
用法
zxcrypt 包含驱动程序和库
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++ 框架 编写司机的典型任务。它允许作者自动提供 src/lib/ddk 函数指针和回调。
有两小块功能无法用 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 读取响应中的数据 来自底层块设备的网络通过 crypto 必须至少包含 16 个字节的密钥长度, 在语义上是安全的 (IND-CCA2),并将块偏移量作为 “tweak”。目前使用的是 AES256-XTS。
环和事务
为了确保对原始 I/O 请求者的数据加密和解密保持透明, 工作器必须在转换数据时复制数据。通过管道发送的 I/O 请求不是 实际上是原始请求用于封装原始文件的请求 请求。
由于需要影子请求,因此影子请求会由 VMO。当 工作器需要转换数据,它对来自原始封装写入的数据进行加密 或者将影子请求中的数据解密到原始请求, 封装读取请求。待原始请求可以交还给原始请求后 请求者,影子请求会被取消分配,其页面也会停用。 这可确保使用的内存不超过未完成 I/O 请求所需的内存。
超级块格式
用于加密和解密数据的密钥材料称为数据密钥,
存储在设备的预留部分 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 盐(如下所述)。
- 版本:用于指示要使用的加密算法。
- 密封密钥:由封装密钥加密的数据密钥(如下所述)。
- 预留:用于将超块与块边界对齐的未使用数据。
- HMAC:截至此时刻的超级块的键控摘要(包括预留字段)。
封装密钥、封装 IV 和 HMAC 密钥都派生自 KDF。此 KDF 是 RFC 5869 HKDF, 将提供的键与“salt”相结合实例 GUID 和“每次使用”标签(例如“wrap”)或 “hmac”。KDF 不会尝试执行任何速率限制。KDF 可降低密钥重复使用的风险 作为新的随机实例盐,将会产生新的派生密钥。通过 HMAC 可防止对 而不泄露任何有关 zxcrypt 密钥的有用信息。
_注意:KDF 不会执行任何按键扩展。攻击者可以 移除设备并尝试自行尝试密钥派生,从而绕过 HMAC 检查和任何 可能的速率限制为防止出现这种情况,zxcrypt 使用方应该添加适当的速率限制 设备密钥,如来派生 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 代码会检查内部边界处是否存在许多错误情况,并返回 信息性错误。效果方面 只是程序员错误, 已转换为“debug”在发布模式下跳过的断言