内联加密

背景

内联加密硬件逻辑上位于内存和磁盘之间,可以在数据进出磁盘时对数据进行加密/解密。对于每个 I/O 请求,软件可以精确控制内联加密硬件如何加密/解密数据,包括密钥、算法、数据单元大小(加密/解密的粒度)和数据单元号(确定初始化向量的值)。

一些内联加密硬件接受所有加密参数,包括低级 I/O 请求中的原始密钥。然而,大多数内联加密硬件都有固定数量的“密钥槽”,并且要求首先将密钥、算法和数据单元大小编程到密钥槽中。然后,每个低级 I/O 请求只包含一个密钥槽索引和数据单元号。

请注意,内联加密硬件与传统的加密加速器非常不同,后者通过内核加密 API 支持。传统的加密加速器对内存区域进行操作,而内联加密硬件对 I/O 请求进行操作。因此,内联加密硬件需要由块层管理,而不是内核加密 API。

内联加密硬件也与“自加密驱动器”非常不同,例如基于 TCG Opal 或 ATA 安全标准的驱动器。自加密驱动器不提供对加密的细粒度控制,并且无法验证生成的密文的正确性。内联加密硬件提供对加密的细粒度控制,包括为每个扇区选择密钥和初始化向量,并且可以测试其正确性。

目标

我们希望在内核中支持内联加密。为了使测试更容易,我们还希望在没有实际内联加密硬件时,支持回退到内核加密 API。我们还希望内联加密能够与分层设备(如 device-mapper 和 loopback)一起工作(即,我们希望能够使用底层设备(如果存在)的内联加密硬件,否则回退到加密 API 加密/解密)。

约束和说明

  • 我们需要一种方法让上层(例如文件系统)指定用于加密/解密 bio 的加密上下文,并且设备驱动程序(例如 UFSHCD)在处理请求时需要能够使用该加密上下文。加密上下文还对 bio 合并引入了约束;块层需要知道这些约束。

  • 不同的内联加密硬件具有不同的支持算法、支持的数据单元大小、最大数据单元号等。我们将这些属性称为“加密能力”。我们需要一种方法让设备驱动程序以通用方式向上层通告加密能力。

  • 内联加密硬件通常(但并非总是)要求在使用前将密钥编程到密钥槽中。由于编程密钥槽可能很慢,并且可能没有很多密钥槽,因此我们不应该只为每个 I/O 请求编程密钥,而是应该跟踪哪些密钥在密钥槽中,并在可能的情况下重用已编程的密钥槽。

  • 上层通常定义加密密钥的特定生命周期结束,例如,当加密目录被锁定或加密映射被删除时。在这些时候,密钥会从内存中擦除。我们必须提供一种方法让上层也从它们所在的任何密钥槽中逐出密钥。

  • 在可能的情况下,device-mapper 设备必须能够传递其底层设备的内联加密支持。但是,device-mapper 设备本身拥有密钥槽是没有意义的。

基本设计

我们引入 struct blk_crypto_key 来表示内联加密密钥以及如何使用它。这包括密钥的类型(原始或硬件封装);密钥的实际字节;密钥的大小;密钥将使用的算法和数据单元大小;以及表示密钥将使用的最大数据单元号所需的字节数。

我们引入 struct bio_crypt_ctx 来表示加密上下文。它包含一个数据单元号和一个指向 blk_crypto_key 的指针。我们将指向 bio_crypt_ctx 的指针添加到 struct biostruct request;这允许块层的用户(例如文件系统)在创建 bio 时提供加密上下文,并将其传递到堆栈中,以供块层和设备驱动程序处理。请注意,加密上下文没有明确说明是加密还是解密,因为这隐含在 bio 的方向中;WRITE 表示加密,READ 表示解密。

我们还引入 struct blk_crypto_profile 来包含特定内联加密设备的所有通用内联加密相关状态。blk_crypto_profile 用作内联加密硬件驱动程序通告其加密能力并向上层提供某些函数(例如,编程和逐出密钥的函数)的方式。想要支持内联加密的每个设备驱动程序都将构造一个 blk_crypto_profile,然后将其与磁盘的 request_queue 相关联。

blk_crypto_profile 还管理硬件的密钥槽(如果适用)。这发生在块层中,以便块层的用户可以只指定加密上下文,而无需了解密钥槽,设备驱动程序也无需关心密钥槽管理的大部分细节。

具体来说,对于每个密钥槽,块层(通过 blk_crypto_profile)跟踪该密钥槽包含哪个 blk_crypto_key(如果有),以及有多少正在进行的 I/O 请求正在使用它。当块层为具有加密上下文的 bio 创建一个 struct request 时,它会获取一个已经包含密钥的密钥槽(如果可能)。否则,它会等待一个空闲密钥槽(一个未被任何 I/O 使用的密钥槽),然后使用设备驱动程序提供的函数将密钥编程到最近最少使用的空闲密钥槽中。在这两种情况下,生成的密钥槽都存储在 request 的 crypt_keyslot 字段中,设备驱动程序可以在其中访问它,并在请求完成后释放它。

struct request 还包含一个指向原始 bio_crypt_ctx 的指针。请求可以由多个 bio 构建,块层在尝试合并 bio 和请求时必须考虑加密上下文。要合并两个 bio/请求,它们必须具有兼容的加密上下文:都未加密,或者都使用相同的密钥和连续的数据单元号进行加密。只保留请求中第一个 bio 的加密上下文,因为已验证剩余的 bio 与第一个 bio 具有合并兼容性。

为了使内联加密能够与基于 request_queue 的分层设备一起工作,当克隆请求时,其加密上下文也会被克隆。当提交克隆的请求时,它会像往常一样被处理;这包括从克隆的目标设备获取密钥槽(如果需要)。

blk-crypto-fallback

上层(例如文件系统)的内联加密支持最好能在没有真正的内联加密硬件的情况下进行测试,块层的密钥槽管理逻辑也是如此。最好还允许上层始终只使用内联加密,而不必以多种方式实现加密。

因此,我们还引入了 blk-crypto-fallback,它是使用内核加密 API 实现的内联加密。blk-crypto-fallback 内置于块层中,因此它可以在任何块设备上工作,而无需任何特殊设置。本质上,当具有加密上下文的 bio 被提交到不支持该加密上下文的 block_device 时,块层将使用 blk-crypto-fallback 处理 bio 的加密/解密。

对于加密,数据不能就地加密,因为调用者通常依赖于它未被修改。相反,blk-crypto-fallback 会分配反弹页面,用这些反弹页面填充一个新的 bio,将数据加密到这些反弹页面中,并提交该“反弹”bio。当反弹 bio 完成时,blk-crypto-fallback 会完成原始 bio。如果原始 bio 太大,可能需要多个反弹 bio;请参阅代码以了解详细信息。

对于解密,blk-crypto-fallback 用自己的回调函数包装 bio 的完成回调函数(bi_complete)和私有数据(bi_private),取消设置 bio 的加密上下文,然后提交 bio。如果读取成功完成,blk-crypto-fallback 会恢复 bio 的原始完成回调函数和私有数据,然后使用内核加密 API 就地解密 bio 的数据。解密从工作队列发生,因为它可能会休眠。之后,blk-crypto-fallback 会完成 bio。

在这两种情况下,blk-crypto-fallback 提交的 bio 都不再具有加密上下文。因此,较低层只看到标准的未加密 I/O。

blk-crypto-fallback 还定义了自己的 blk_crypto_profile,并拥有自己的“密钥槽”;它的密钥槽包含 struct crypto_skcipher 对象。这样做的原因有两个。首先,它允许在没有实际内联加密硬件的情况下测试密钥槽管理逻辑。其次,与实际的内联加密硬件类似,加密 API 不直接在请求中接受密钥,而是要求提前设置密钥,并且设置密钥可能会很昂贵;此外,由于它占用的锁,因此根本无法在 I/O 路径上分配 crypto_skcipher。因此,密钥槽的概念对于 blk-crypto-fallback 仍然有意义。

请注意,无论使用真正的内联加密硬件还是 blk-crypto-fallback,写入磁盘的密文(以及因此数据的磁盘格式)都将是相同的(假设内联加密硬件的实现和内核加密 API 使用的算法的实现都符合规范并正常运行)。

blk-crypto-fallback 是可选的,由 CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK 内核配置选项控制。

呈现给块层用户的API

blk_crypto_config_supported() 允许用户提前检查使用特定加密设置的内联加密是否可以在特定的 block_device 上工作 -- 通过硬件或通过 blk-crypto-fallback。此函数接受一个 struct blk_crypto_config,它类似于 blk_crypto_key,但省略了密钥的实际字节,而只包含算法、数据单元大小等。如果禁用了 blk-crypto-fallback,则此函数可能很有用。

blk_crypto_init_key() 允许用户初始化 blk_crypto_key。

用户必须在实际开始在 block_device 上使用 blk_crypto_key 之前调用 blk_crypto_start_using_key()(即使之前调用了 blk_crypto_config_supported())。这是初始化 blk-crypto-fallback(如果需要)所必需的。这不能从数据路径调用,因为这可能必须分配资源,在这种情况下可能会死锁。

接下来,要将加密上下文附加到 bio,用户应调用 bio_crypt_set_ctx()。此函数分配一个 bio_crypt_ctx 并将其附加到 bio,给定 blk_crypto_key 和将用于加密/解密的数据单元号。用户无需担心稍后释放 bio_crypt_ctx,因为这会在 bio 被释放或重置时自动发生。

最后,当完成在 block_device 上使用带有 blk_crypto_key 的内联加密时,用户必须调用 blk_crypto_evict_key()。这确保了密钥从它可能被编程到的所有密钥槽中逐出,并且与它可能链接到的任何内核数据结构取消链接。

总之,对于块层的用户,blk_crypto_key 的生命周期如下

  1. blk_crypto_config_supported()(可选)

  2. blk_crypto_init_key()

  3. blk_crypto_start_using_key()

  4. bio_crypt_set_ctx()(可能多次)

  5. blk_crypto_evict_key()(在所有 I/O 完成后)

  6. 将 blk_crypto_key 归零(这没有专用函数)

如果 blk_crypto_key 正在多个 block_device 上使用,则必须在每个 block_device 上调用 blk_crypto_config_supported()(如果使用)、blk_crypto_start_using_key()blk_crypto_evict_key()

呈现给设备驱动程序的API

想要支持内联加密的设备驱动程序必须在其设备的 request_queue 中设置一个 blk_crypto_profile。为此,它首先必须调用 blk_crypto_profile_init()(或其资源管理变体 devm_blk_crypto_profile_init()),提供密钥槽的数量。

接下来,它必须通过设置 blk_crypto_profile 中的字段来通告其加密能力,例如 modes_supportedmax_dun_bytes_supported

然后,它必须在 blk_crypto_profile 的 ll_ops 字段中设置函数指针,以告知上层如何控制内联加密硬件,例如如何编程和逐出密钥槽。大多数驱动程序都需要实现 keyslot_programkeyslot_evict。有关详细信息,请参阅 struct blk_crypto_ll_ops 的注释。

一旦驱动程序向 request_queue 注册了一个 blk_crypto_profile,驱动程序通过该队列接收的 I/O 请求可能具有加密上下文。所有加密上下文都将与 blk_crypto_profile 中声明的加密能力兼容,因此驱动程序无需担心处理不受支持的请求。此外,如果在 blk_crypto_profile 中声明了非零数量的密钥槽,则所有具有加密上下文的 I/O 请求也将具有一个密钥槽,该密钥槽已编程了适当的密钥。

如果驱动程序实现了运行时挂起,并且其 blk_crypto_ll_ops 在设备运行时挂起时不起作用,则驱动程序还必须设置 blk_crypto_profile 的 dev 字段,以指向将在任何低级操作被调用之前恢复的 struct device

如果存在内联加密硬件丢失其密钥槽内容的情况(例如设备重置),则驱动程序必须处理重新编程密钥槽。为此,驱动程序可以调用 blk_crypto_reprogram_all_keys()

最后,如果驱动程序使用了 blk_crypto_profile_init() 而不是 devm_blk_crypto_profile_init(),则它负责在不再需要加密配置文件时调用 blk_crypto_profile_destroy()

分层设备

基于请求队列的分层设备(如 dm-rq)想要支持内联加密需要为其 request_queue 创建自己的 blk_crypto_profile,并公开他们选择的任何功能。当分层设备想要将该请求的克隆传递到另一个 request_queue 时,blk-crypto 将根据需要初始化和准备该克隆。

内联加密和 blk 完整性之间的交互

在本文档发布之时,没有真正的硬件同时支持这两个功能。但是,这些功能确实相互交互,并且使它们能够正确地协同工作并非完全琐碎。特别是,当 WRITE bio 想要在使用内联加密的设备上使用内联加密时,将指定 bio 的加密上下文,之后计算其完整性信息(使用明文数据,因为加密将在写入数据时发生),并将数据和完整性信息发送到设备。显然,必须在加密数据之前验证完整性信息。加密数据后,设备不得存储它收到的带有明文数据的完整性信息,因为这可能会泄露有关明文数据的信息。因此,它必须从密文数据重新生成完整性信息,并将其存储在磁盘上。存储明文数据的完整性信息的另一个问题是,它会根据是否存在硬件内联加密支持或是否使用内核加密 API 回退来更改磁盘格式(因为如果使用回退,设备将接收密文的完整性信息,而不是明文的完整性信息)。

因为还没有任何真正的硬件,所以假设硬件实现可能无法正确地一起实现这两个功能,并且暂时禁止这种组合似乎是明智的。每当设备支持完整性时,内核将假装该设备不支持硬件内联加密(通过将设备的 request_queue 中的 blk_crypto_profile 设置为 NULL)。当启用加密 API 回退时,这意味着所有具有加密上下文的 bio 都将使用回退,并且 IO 将像往常一样完成。当禁用回退时,具有加密上下文的 bio 将失败。

硬件封装密钥

动机和威胁模型

Linux 存储加密(dm-crypt、fscrypt、eCryptfs 等)传统上依赖于原始加密密钥存在于内核内存中,以便可以执行加密。传统上,这不被视为一个问题,因为在离线攻击期间密钥不会存在,而这正是存储加密旨在防止的主要攻击类型。

但是,越来越希望(在可能的范围内)保护用户的数据免受其他类型的攻击,包括

  • 冷启动攻击,攻击者可以物理访问系统,突然关闭电源,然后立即转储系统内存以提取最近使用的加密密钥,然后使用这些密钥解密磁盘上的用户数据。

  • 在线攻击,攻击者能够在不完全破坏系统的情况下读取内核内存,然后进行离线攻击,其中提取的任何密钥都可用于解密磁盘上的用户数据。这种在线攻击的一个例子是,如果攻击者能够在系统上运行一些代码,该代码利用了类似 Meltdown 的漏洞,但无法提升权限。

  • 在线攻击,攻击者完全破坏了系统,但其数据泄露在时间上受到严重限制和/或带宽受到限制,因此为了完全泄露数据,他们需要提取加密密钥以供以后进行离线攻击。

硬件封装密钥是内联加密硬件的一个功能,旨在(在可能的范围内)保护用户的数据免受上述攻击,而不会引入诸如最大密钥数量之类的限制。

请注意,不可能完全保护用户的数据免受这些攻击。即使在攻击者“只是”获得对内核内存的读取访问权限的攻击中,他们仍然可以提取内存中存在的任何用户数据,包括加密文件的明文 pagecache 页面。这里的重点只是保护加密密钥,因为这些密钥会立即访问任何后续离线攻击中的所有用户数据,而不仅仅是一些数据(其中哪些数据包含在该“一些”中可能不受攻击者控制)。

解决方案概述

内联加密硬件通常具有“密钥槽”,软件可以将密钥编程到其中以供硬件使用;密钥槽的内容通常无法被软件读回。因此,如果内核在将密钥编程到密钥槽中后简单地擦除了其密钥副本,并且此后仅通过密钥槽号引用它们,则可以实现上述安全目标。

但是,这种幼稚的方法会遇到几个问题

  • 它将解锁的密钥数量限制为密钥槽的数量,这通常是一个很小的数字。在整个系统中只有一个加密密钥的情况下(例如,全盘加密密钥),这是可以容忍的。但是,通常可能有很多登录用户,他们有许多不同的密钥,并且/或者有很多运行的应用程序,它们具有特定于应用程序的加密存储区域。如果正在使用基于文件的加密(例如 fscrypt),则尤其如此。

  • 如果存储控制器(通常是 UFS 或 eMMC)被重置,内联加密引擎通常会丢失其密钥槽的内容。重置存储控制器是标准错误恢复程序,如果发生某些类型的存储错误,就会执行该程序,并且此类错误可能随时发生。因此,当使用内联加密时,操作系统必须始终准备好在没有用户干预的情况下重新编程密钥槽。

因此,内核仍然需要一种方法来“提醒”硬件关于密钥,而实际上没有原始密钥本身。

稍微不重要的是,原始密钥也永远不会对软件可见,即使在最初解锁时也是如此。这将确保对系统内存的只读破坏永远不会允许提取密钥以供系统外使用,即使它发生在解锁密钥时也是如此。

为了解决所有这些问题,一些内联加密硬件供应商使其硬件支持 硬件封装密钥。硬件封装密钥是加密的密钥,只能由硬件解包(解密)和使用 -- 无论是通过内联加密硬件本身,还是通过可以直接向内联加密硬件提供密钥的专用硬件块。

(我们称它们为“硬件封装密钥”,而不是简单地称为“封装密钥”,以便在可能存在其他类型的封装密钥(例如在基于文件的加密中)的情况下增加一些清晰度。密钥封装是一种常用的技术。)

封装(加密)硬件封装密钥的密钥是硬件内部密钥,永远不会暴露给软件;它要么是持久密钥(“长期封装密钥”),要么是每次启动密钥(“临时封装密钥”)。密钥的长期封装形式是最初解锁的形式,但一旦它被转换为临时封装密钥,就会从内存中擦除。使用中的硬件封装密钥始终是临时封装的,而不是长期封装的。

由于内联加密硬件只能用于加密/解密磁盘上的数据,因此硬件还包括一个间接级别;它不直接使用解封的密钥进行内联加密,而是从中派生出一个内联加密密钥和一个“软件秘密”。软件可以使用“软件秘密”来执行无法使用内联加密硬件的任务,例如文件名加密。软件秘密不受内存破坏的影响。

密钥层次结构

这是硬件封装密钥的密钥层次结构

               Hardware-wrapped key
                        |
                        |
                  <Hardware KDF>
                        |
          -----------------------------
          |                           |
Inline encryption key           Software secret

组件是

  • 硬件封装密钥:硬件 KDF(密钥派生函数)的密钥,以临时封装形式。密钥封装算法是硬件实现细节,不会影响内核操作,但建议使用强大的经过身份验证的加密算法,例如 AES-256-GCM。

  • 硬件 KDF:KDF(密钥派生函数),硬件在解封装封装密钥后使用该函数来派生子密钥。硬件选择的 KDF 不会影响内核操作,但需要为了测试目的而了解它,并且还假定它至少具有 256 位的安全强度。所有已知的硬件都使用 AES-256-CMAC 在计数器模式下使用 SP800-108 KDF,并具有特定的标签和上下文选择;新的硬件应使用这种已经过审查的 KDF。

  • 内联加密密钥:硬件直接向内联加密硬件的密钥槽提供的派生密钥,而不会将其暴露给软件。在所有已知的硬件中,这将始终是一个 AES-256-XTS 密钥。但是,原则上也可以支持其他加密算法。硬件必须为每个支持的加密算法派生不同的子密钥。

  • 软件秘密:硬件返回给软件的派生密钥,以便软件可以将其用于无法使用内联加密的加密任务。该值在密码学上与内联加密密钥隔离,即,了解一个不会泄露另一个。(KDF 确保了这一点。)目前,软件秘密始终为 32 字节,因此适用于需要高达 256 位安全强度的加密应用程序。一些用例(例如全盘加密)不需要软件秘密。

示例:在 fscrypt 的情况下,fscrypt 主密钥(保护一组特定加密目录的密钥)被设为硬件封装密钥。内联加密密钥用作文件内容加密密钥,而软件秘密(而不是直接使用主密钥)用于为 fscrypt 的 KDF (HKDF-SHA512) 提供密钥,以派生其他子密钥,例如文件名加密密钥。

请注意,目前此设计假定每个硬件封装密钥只有一个内联加密密钥,而没有任何进一步的密钥派生。因此,在 fscrypt 的情况下,目前硬件封装密钥仅与“内联加密优化”设置兼容,该设置对每个加密策略使用一个文件内容加密密钥,而不是每个文件使用一个密钥。可以扩展此设计,以使硬件使用传递到存储堆栈的每个文件 nonce 派生每个文件密钥,并且实际上一些硬件已经支持此功能;计划在未来的工作中通过添加相应的内核支持来消除此限制。

内核支持

内核块层(“blk-crypto”)的内联加密支持已扩展为支持硬件封装密钥,作为原始密钥的替代方案(在硬件支持可用时)。其工作方式如下

  • 一个 key_types_supported 字段被添加到 struct blk_crypto_profile 中的加密能力中。这允许设备驱动程序声明它们支持原始密钥、硬件封装密钥或两者都支持。

  • struct blk_crypto_key 现在可以包含硬件封装密钥,作为原始密钥的替代方案;一个 key_type 字段被添加到 struct blk_crypto_config 中,以区分不同的密钥类型。这允许 blk-crypto 的用户以与使用原始密钥非常相似的方式使用硬件封装密钥加密/解密数据。

  • 添加了一个新的方法 blk_crypto_ll_ops::derive_sw_secret。支持硬件封装密钥的设备驱动程序必须实现此方法。blk-crypto 的用户可以调用 blk_crypto_derive_sw_secret() 来访问此方法。

  • 硬件封装密钥的编程和逐出通过 blk_crypto_ll_ops::keyslot_programblk_crypto_ll_ops::keyslot_evict 进行,就像对原始密钥所做的那样。如果驱动程序支持硬件封装密钥,则它必须处理传递给这些方法的硬件封装密钥。

blk-crypto-fallback 不支持硬件封装密钥。因此,硬件封装密钥只能与真正的内联加密硬件一起使用。

以上所有内容仅涉及临时封装形式的硬件封装密钥。为了首先获得此类密钥,添加了新的块设备 ioctl,以提供创建和准备此类密钥的通用接口

  • BLKCRYPTOIMPORTKEY 将原始密钥转换为长期包装形式。它接受指向 struct blk_crypto_import_key_arg 的指针。调用者必须将 raw_key_ptrraw_key_size 设置为要导入的原始密钥的指针和大小(以字节为单位)。成功时,BLKCRYPTOIMPORTKEY 返回 0,并将生成的长期包装密钥 blob 写入由 lt_key_ptr 指向的缓冲区,该缓冲区的最大大小为 lt_key_size。它还会更新 lt_key_size 为密钥的实际大小。失败时,它返回 -1 并设置 errno。 EOPNOTSUPP 的 errno 表示块设备不支持硬件包装密钥。 EOVERFLOW 的 errno 表示输出缓冲区没有足够的空间来容纳密钥 blob。

  • BLKCRYPTOGENERATEKEY 类似于 BLKCRYPTOIMPORTKEY,但它让硬件生成密钥而不是导入密钥。它接受指向 struct blk_crypto_generate_key_arg 的指针。

  • BLKCRYPTOPREPAREKEY 将密钥从长期包装形式转换为临时包装形式。它接受指向 struct blk_crypto_prepare_key_arg 的指针。调用者必须将 lt_key_ptrlt_key_size 设置为要转换的长期包装密钥 blob 的指针和大小(以字节为单位)。成功时,BLKCRYPTOPREPAREKEY 返回 0,并将生成的临时包装密钥 blob 写入由 eph_key_ptr 指向的缓冲区,该缓冲区的最大大小为 eph_key_size。它还会更新 eph_key_size 为密钥的实际大小。失败时,它返回 -1 并设置 errno。 EOPNOTSUPPEOVERFLOW 的 Errno 值含义与 BLKCRYPTOIMPORTKEY 相同。 EBADMSG 的 errno 表示长期包装密钥无效。

用户空间需要使用 BLKCRYPTOIMPORTKEYBLKCRYPTOGENERATEKEY 一次来创建密钥,然后在每次解锁密钥并将其添加到内核时使用 BLKCRYPTOPREPAREKEY。请注意,这些 ioctl 与原始密钥无关;它们仅适用于硬件包装密钥。

可测试性

硬件 KDF 和内联加密本身都是定义明确的算法,除了未包装的密钥外,不依赖于任何秘密。因此,如果软件已知未包装的密钥,则可以在软件中重现这些算法,以验证内联加密硬件写入磁盘的密文。

但是,只有在使用“导入”功能时,软件才能知道未包装的密钥。在硬件自行生成密钥的“生成”情况下,无法进行适当的测试。“生成”模式的正确操作因此依赖于硬件 RNG 的安全性和正确性及其用于生成密钥的方式,以及“导入”模式的测试,因为该测试应涵盖除密钥生成之外的所有部分。

有关验证以“导入”模式写入磁盘的密文的测试示例,请参见 xfstests 中的 fscrypt 硬件包装密钥测试,或 Android 的 vts_kernel_encryption_test