硬件封装密钥

与大多数磁盘和文件加密软件一样,Android 的存储加密传统上依赖于系统内存中存在的原始加密密钥,以便可以执行加密。即使加密是由专用硬件而不是软件执行的,软件通常仍然需要管理原始加密密钥。

传统上,这不被视为问题,因为密钥在离线攻击期间不会存在,而离线攻击是存储加密旨在防御的主要攻击类型。但是,人们希望增强对其他类型攻击(例如冷启动攻击)以及在线攻击的保护,在在线攻击中,攻击者可能能够在不完全入侵设备的情况下泄露系统内存。

为了解决这个问题,Android 11 引入了对硬件封装密钥的支持(如果存在硬件支持)。硬件封装密钥是仅在专用硬件中以原始形式知道的存储密钥;软件仅以封装(加密)形式查看和使用这些密钥。此硬件必须能够生成和导入存储密钥、以临时和长期形式封装存储密钥、派生子密钥、将一个子密钥直接编程到内联加密引擎中,以及将单独的子密钥返回给软件。

注意内联加密引擎(或内联加密硬件)是指在数据往返存储设备的过程中对数据进行加密/解密的硬件。通常,这是 UFS 或 eMMC 主机控制器,它实现了相应 JEDEC 规范定义的加密扩展。

设计

本节介绍硬件封装密钥功能的设计,包括实现该功能所需的硬件支持。本讨论重点关注基于文件的加密 (FBE),但该解决方案也适用于元数据加密

避免在系统内存中需要原始加密密钥的一种方法是仅将它们保存在内联加密引擎的密钥槽中。但是,这种方法遇到了一些问题

  • 加密密钥的数量可能超过密钥槽的数量。
  • 如果存储控制器(通常为 UFS 或 eMMC)重置,则内联加密引擎通常会丢失其密钥槽的内容。重置存储控制器是标准的错误恢复程序,如果在发生某些类型的存储错误时执行该程序,并且此类错误可能随时发生。因此,当使用内联加密时,操作系统必须始终准备好在无需用户干预的情况下重新编程密钥槽。
  • 内联加密引擎只能用于加密/解密磁盘上的完整数据块。但是,在 FBE 的情况下,软件仍然需要能够执行其他加密工作,例如文件名加密和派生密钥标识符。软件仍然需要访问原始 FBE 密钥才能执行其他工作。

为了避免这些问题,存储密钥改为硬件封装密钥,该密钥只能由专用硬件解封和使用。这允许支持无限数量的密钥。此外,修改了密钥层次结构并将其部分移动到此硬件,这允许将子密钥返回给软件以用于无法使用内联加密引擎的任务。

密钥层次结构

密钥可以使用KDF(密钥派生函数)(例如HKDF)从其他密钥派生,从而生成密钥层次结构

下图描述了使用硬件封装密钥时 FBE 的典型密钥层次结构

FBE key hierarchy (standard)
图 1. FBE 密钥层次结构(标准)

FBE 类密钥是 Android 传递给 Linux 内核以解锁特定加密目录集(例如特定 Android 用户的凭据加密存储)的原始加密密钥。(在内核中,此密钥称为fscrypt 主密钥。)内核从此密钥派生以下子密钥

  • 密钥标识符。这不用于加密,而是一个用于标识特定文件或目录受保护的密钥的值。
  • 文件内容加密密钥
  • 文件名加密密钥

相比之下,下图描述了使用硬件封装密钥时 FBE 的密钥层次结构

FBE key hierarchy (with hardware-wrapped key)
图 2. FBE 密钥层次结构(使用硬件封装密钥)

与之前的案例相比,密钥层次结构中添加了一个额外的级别,并且文件内容加密密钥已重新定位。根节点仍然代表 Android 传递给 Linux 以解锁一组加密目录的密钥。但是,现在该密钥采用临时封装形式,为了使用它,必须将其传递给专用硬件。此硬件必须实现两个接受临时封装密钥的接口

  • 一个接口用于派生 inline_encryption_key 并将其直接编程到内联加密引擎的密钥槽中。这允许在软件无权访问原始密钥的情况下加密/解密文件内容。在 Android 通用内核中,此接口对应于 blk_crypto_ll_ops::keyslot_program 操作,该操作必须由存储驱动程序实现。
  • 一个接口用于派生和返回 sw_secret(“软件密钥” - 在某些地方也称为“原始密钥”),这是 Linux 用于派生除文件内容加密之外的所有子密钥的密钥。在 Android 通用内核中,此接口对应于 blk_crypto_ll_ops::derive_sw_secret 操作,该操作必须由存储驱动程序实现。

为了从原始存储密钥派生 inline_encryption_keysw_secret,硬件必须使用密码学上强大的 KDF。此 KDF 必须遵循密码学最佳实践;它必须具有至少 256 位的安全强度,即足以满足以后使用的任何算法。它还必须在派生每种类型的子密钥时使用不同的标签、上下文和特定于应用的信息字符串,以保证生成的子密钥在密码学上是隔离的,也就是说,了解其中一个子密钥不会泄露任何其他子密钥。密钥拉伸不是必需的,因为原始存储密钥已经是均匀随机密钥。

从技术上讲,可以使用满足安全要求的任何 KDF。但是,出于测试目的,有必要在测试代码中重新实现相同的 KDF。目前,已经审查和实施了一个 KDF;它可以在 vts_kernel_encryption_test 的源代码中找到。建议硬件使用此 KDF,它使用NIST SP 800-108 “计数器模式下的 KDF”,其中 AES-256-CMAC 作为 PRF。请注意,为了兼容,算法的所有部分都必须相同,包括每个子密钥的 KDF 上下文和标签的选择。

密钥封装

为了满足硬件封装密钥的安全目标,定义了两种类型的密钥封装

  • 临时封装:硬件使用在每次启动时随机生成且不直接在硬件外部公开的密钥对原始密钥进行加密。
  • 长期封装:硬件使用内置于硬件且不直接在硬件外部公开的唯一持久密钥对原始密钥进行加密。

传递给 Linux 内核以解锁存储的所有密钥都采用临时封装形式。这确保了如果攻击者能够从系统内存中提取正在使用的密钥,则该密钥不仅在设备外部不可用,而且在重启后在设备上也不可用。

同时,Android 仍然需要能够在磁盘上存储密钥的加密版本,以便可以首先解锁它们。原始密钥可以用于此目的。但是,最好永远不要让原始密钥出现在系统内存中,这样它们就永远不会被提取以在设备外部使用,即使是在启动时提取也是如此。因此,定义了长期封装的概念。

为了支持管理以这两种不同方式封装的密钥,硬件必须实现以下接口

  • 生成和导入存储密钥的接口,以长期封装形式返回它们。这些接口通过 KeyMint 间接访问,它们对应于 TAG_STORAGE_KEY KeyMint 标记。“生成”功能由 vold 使用,以生成供 Android 使用的新存储密钥,而“导入”功能由 vts_kernel_encryption_test 使用,以导入测试密钥。
  • 将长期封装的存储密钥转换为临时封装的存储密钥的接口。这对应于 convertStorageKeyToEphemeral KeyMint 方法。此方法由 voldvts_kernel_encryption_test 使用,以便解锁存储。

密钥封装算法是一个实现细节,但它应使用强大的 AEAD(例如 AES-256-GCM)和随机 IV。

所需的软件更改

AOSP 已经有一个支持硬件封装密钥的基本框架。这包括用户空间组件(如 vold)中的支持,以及 Linux 内核中 blk-cryptofscryptdm-default-key 中的支持。

但是,需要进行一些特定于实现的更改。

KeyMint 更改

必须修改设备的 KeyMint 实现以支持 TAG_STORAGE_KEY 并实现 convertStorageKeyToEphemeral 方法。

在 Keymaster 中,使用 exportKey 代替 convertStorageKeyToEphemeral

Linux 内核更改

必须修改设备内联加密引擎的 Linux 内核驱动程序以支持硬件封装密钥。

对于 android14 及更高版本的内核,在 blk_crypto_profile::key_types_supported 中设置 BLK_CRYPTO_KEY_TYPE_HW_WRAPPED,使 blk_crypto_ll_ops::keyslot_programblk_crypto_ll_ops::keyslot_evict 支持编程/驱逐硬件封装密钥,并实现 blk_crypto_ll_ops::derive_sw_secret

对于 android12android13 内核,在 blk_keyslot_manager::features 中设置 BLK_CRYPTO_FEATURE_WRAPPED_KEYS,使 blk_ksm_ll_ops::keyslot_programblk_ksm_ll_ops::keyslot_evict 支持编程/驱逐硬件封装密钥,并实现 blk_ksm_ll_ops::derive_raw_secret

对于 android11 内核,在 keyslot_manager::features 中设置 BLK_CRYPTO_FEATURE_WRAPPED_KEYS,使 keyslot_mgmt_ll_ops::keyslot_programkeyslot_mgmt_ll_ops::keyslot_evict 支持编程/驱逐硬件封装密钥,并实现 keyslot_mgmt_ll_ops::derive_raw_secret

测试

尽管使用硬件封装密钥进行加密比使用标准密钥进行加密更难测试,但仍然可以通过导入测试密钥并重新实现硬件执行的密钥派生来进行测试。这在 vts_kernel_encryption_test 中实现。要运行此测试,请运行

atest -v vts_kernel_encryption_test

读取测试日志并验证硬件封装密钥测试用例(例如,FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicyDmDefaultKeyTest.TestHwWrappedKey)是否未因未检测到对硬件封装密钥的支持而被跳过,因为在这种情况下测试结果仍然为“通过”。

启用密钥

一旦设备的硬件封装密钥支持正常工作,您就可以对设备的 fstab 文件进行以下更改,以使 Android 将其用于 FBE 和元数据加密

  • FBE:将 wrappedkey_v0 标志添加到 fileencryption 参数。例如,使用 fileencryption=::inlinecrypt_optimized+wrappedkey_v0。有关更多详细信息,请参阅 FBE 文档
  • 元数据加密:将 wrappedkey_v0 标志添加到 metadata_encryption 参数。例如,使用 metadata_encryption=:wrappedkey_v0。有关更多详细信息,请参阅元数据加密文档