元数据加密

Android 7.0 及更高版本支持文件级加密 (FBE)。采用 FBE 时,可以使用不同的密钥对不同的文件进行加密,也可以对加密文件单独解密。这些密钥会用于加密文件内容和文件名。 采用 FBE 时,其他信息(例如目录布局、文件大小、权限和创建/修改时间)不会被加密。这些其他信息统称为“文件系统元数据”。

Android 9 引入了对元数据加密的支持。借助元数据加密,启动时即可用的单个密钥会加密所有未通过 FBE 加密的内容。该密钥受到 Keymaster 的保护,而 Keymaster 受到启动时验证功能的保护。

每当启用 FBE 时,可合并的存储设备上总是会启用元数据加密。 您还可以在内部存储设备上启用元数据加密。对于搭载 Android 11 或更高版本的设备,必须在内部存储设备上启用元数据加密。

内部存储设备上的实现

为了在新设备的内部存储设备中设置元数据加密,您首先需要设置 metadata 文件系统,更改 init 序列,然后在设备的 fstab 文件中启用元数据加密即可。

前提条件

元数据加密只能在数据分区首次进行格式化时设置。因此,该功能仅适用于新设备;OTA 不应更改此设置。

元数据加密要求在内核中启用 dm-default-key 模块。在 Android 11 及更高版本中,Android 通用内核 4.14 及更高版本支持 dm-default-key。此版本的 dm-default-key 使用一种独立于硬件和供应商的加密框架,名为“blk-crypto”。

如需启用 dm-default-key,请使用:

CONFIG_BLK_INLINE_ENCRYPTION=y
CONFIG_FS_ENCRYPTION_INLINE_CRYPT=y
CONFIG_DM_DEFAULT_KEY=y

dm-default-key 在可行情况下使用内嵌加密硬件(用于在数据往返于存储设备的途中对数据进行加密/解密)。您如果不使用内嵌加密硬件,则还需要启用内核加密 API 的回退机制:

CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK=y

如果不使用内嵌加密硬件,您还应按照FBE 文档中的建议,启用任何基于 CPU 的可用加速。

在 Android 10 及更低版本中,Android 通用内核不支持 dm-default-key。因此,应由供应商实现 dm-default-key

设置元数据文件系统

由于在元数据加密密钥可用之前,userdata 分区中的所有内容均无法读取,因此分区表必须留出一个名为“metadata 分区”的单独分区,用于存储保护该密钥的 Keymaster Blob。metadata 分区的大小应为 16MB。

fstab.hardware 必须为该分区上的元数据文件系统纳入一个条目,并将其装载到 /metadata(包括 formattable 标记),以确保在启动时对其进行格式化。f2fs 文件系统不适用于较小的分区;在较小分区中,我们建议您改为使用 ext4。例如:

/dev/block/bootdevice/by-name/metadata              /metadata          ext4        noatime,nosuid,nodev,discard                          wait,check,formattable

如需确保 /metadata 装载点存在,请在 BoardConfig-common.mk 中添加下面这行代码:

BOARD_USES_METADATA_PARTITION := true

更改 init 序列

在使用元数据加密时,必须在装载 /data 之前运行 vold。为了确保其尽早开始运行,请将以下节添加到 init.hardware.rc

# We need vold early for metadata encryption
on early-fs
    start vold

Keymaster 必须在 init 尝试装载 /data 之前运行并准备就绪。

init.hardware.rc 应该已经包含一个 mount_all 指令,用于将 /data 本身装载到 on late-fs 节中。请在这行代码前面添加以下指令,以执行 wait_for_keymaster 服务:

on late-fs
   …
    # Wait for keymaster
    exec_start wait_for_keymaster

    # Mount RW partitions which need run fsck
    mount_all /vendor/etc/fstab.${ro.boot.hardware.platform} --late

开启元数据加密

最后,将 keydirectory=/metadata/vold/metadata_encryption 添加到 userdata 对应 fstab 条目的 fs_mgr_flags 列中。例如,完整的 fstab 行可能如下所示:

/dev/block/bootdevice/by-name/userdata              /data              f2fs        noatime,nosuid,nodev,discard,inlinecrypt latemount,wait,check,fileencryption=aes-256-xts:aes-256-cts:inlinecrypt_optimized,keydirectory=/metadata/vold/metadata_encryption,quota,formattable

默认情况下,内部存储设备上的元数据加密算法为 AES-256-XTS。可以通过设置 metadata_encryption 选项(同样是在 fs_mgr_flags 列中)替换此算法:

  • 在没有采用 AES 加速的设备上,可以通过设置 metadata_encryption=adiantum 启用 Adiantum 加密
  • 在支持硬件封装密钥的设备上,可以通过设置 metadata_encryption=aes-256-xts:wrappedkey_v0(或等效的 metadata_encryption=:wrappedkey_v0,因为默认算法为 aes-256-xts)实现元数据加密密钥的硬件封装。

由于 Android 11 中 dm-default-key 的内核接口发生了变化,因此您还需要确保已在 device.mk 中为 PRODUCT_SHIPPING_API_LEVEL 设置了正确的值。例如,如果您的设备搭载 Android 11(API 级别 30),device.mk 应包含以下代码:

PRODUCT_SHIPPING_API_LEVEL := 30

您还可以设置以下系统属性,以强制使用新的 dm-default-key API(无论出厂 API 级别为何):

PRODUCT_PROPERTY_OVERRIDES += \
    ro.crypto.dm_default_key.options_format.version=2

验证

如需验证元数据加密是否已启用并正常运行,请运行以下测试。另外,请注意下文所述的常见问题

测试

首先,请运行以下命令,验证内部存储设备是否启用了元数据加密:

adb root
adb shell dmctl table userdata

输出的内容应类似于以下文本:

Targets in the device-mapper table for userdata:
0-4194304: default-key, aes-xts-plain64 - 0 252:2 0 3 allow_discards sector_size:4096 iv_large_sectors

如果您通过在设备的 fstab 中设置 metadata_encryption 选项替换默认加密设置,输出将与上述内容略有不同。例如,如果您启用了 Adiantum 加密,那么第三个字段将是 xchacha12,aes-adiantum-plain64,而不是 aes-xts-plain64

接下来,运行 vts_kernel_encryption_test,验证元数据加密和 FBE 的正确性:

atest vts_kernel_encryption_test

或:

vts-tradefed run vts -m vts_kernel_encryption_test

常见问题

在调用 mount_all(用于装载已对元数据加密的 /data 分区)时,init 会执行 vdc 工具。vdc 工具会通过 binder 连接到 vold,以设置元数据加密的设备并装载分区。在此调用期间,init 会被拦截,并且在 mount_all 完成之前,尝试读取或设置 init 属性的操作也会被拦截。 在此阶段,如果读取或设置某个属性时 vold 的任何一部分工作遭到直接或间接拦截,就会导致死锁。请务必确保 vold 能够完成读取密钥、与 Keymaster 交互以及装载数据目录的工作,而无需与 init 进一步交互。

Keymaster 如果在 mount_all 运行时没有完全启动,在从 init 读取到某些属性之前,都不会响应 vold,从而导致上述死锁。按照相关规定将 exec_start wait_for_keymaster 放置在相关的 mount_all 调用之前,可确保 Keymaster 提前完全运行,从而避免此类死锁。

可合并的存储设备上的配置

从 Android 9 起,每当启用 FBE 时,可合并的存储设备上总是会启用元数据加密,即使内部存储设备上未启用元数据加密也是如此。

在 AOSP 中,可合并的存储设备上有两种元数据加密实现:一种是基于 dm-crypt 的已废弃加密实现,另一种是基于 dm-default-key 的较新加密实现。为了确保为您的设备选择正确的实现,请确保已在 device.mk 中为 PRODUCT_SHIPPING_API_LEVEL 设置了正确的值。例如,如果您的设备搭载 Android 11(API 级别 30),device.mk 应包含以下代码:

PRODUCT_SHIPPING_API_LEVEL := 30

您还可以设置以下系统属性,以强制使用新的卷元数据加密方法(以及新的默认 FBE 政策版本),而无论出厂 API 级别为何:

PRODUCT_PROPERTY_OVERRIDES += \
    ro.crypto.volume.metadata.method=dm-default-key \
    ro.crypto.dm_default_key.options_format.version=2 \
    ro.crypto.volume.options=::v2

当前方法

在搭载 Android 11 或更高版本的设备上,可合并的存储设备上的元数据加密会使用 dm-default-key 内核模块,就像在内部存储设备上一样。请参阅上文的前提条件,了解需要启用的内核配置选项。请注意,设备内部存储设备所运行的内嵌加密硬件,也许无法在可合并的存储设备上使用,因此可能需要 CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK=y

默认情况下,dm-default-key 卷元数据加密方法采用 AES-256-XTS 加密算法(搭配 4096 字节的加密扇区)。您可以通过设置 ro.crypto.volume.metadata.encryption 系统属性替换此算法。此属性的值采用与上述 metadata_encryption fstab 选项相同的语法。例如,在没有采用 AES 加速的设备上,可以通过设置 ro.crypto.volume.metadata.encryption=adiantum 启用 Adiantum 加密

旧版方法

在搭载 Android 10 或更低版本的设备上,可合并的存储设备上的元数据加密会使用 dm-crypt 内核模块,而非 dm-default-key

CONFIG_DM_CRYPT=y

dm-default-key 方法不同,dm-crypt 方法会使文件内容被加密两次:一次是使用 FBE 密钥,另一次是使用元数据加密密钥。这种双重加密会降低性能,并且不是实现元数据加密安全目标的必需条件,因为 Android 可确保 FBE 密钥至少和元数据加密密钥一样难以破解。供应商可以进行内核自定义以避免双重加密,具体来说就是实现 allow_encrypt_override 选项(当系统属性 ro.crypto.allow_encrypt_override 设置为 true 时,Android 会将该选项传递到 dm-crypt)。 Android 通用内核不支持这些自定义。

默认情况下,dm-crypt 卷元数据加密方法会采用 AES-128-CBC 加密算法(搭配 ESSIV 和 512 字节的加密扇区)。您可以通过设置以下系统属性(也用于 FDE)替换此配置:

  • ro.crypto.fde_algorithm,用于选择元数据加密算法。选项包括 aes-128-cbcadiantum。只有当设备没有采用 AES 加速时,才能使用 Adiantum
  • ro.crypto.fde_sector_size,用于选择加密扇区大小。选项包括 512、1024、2048 和 4096。对于 Adiantum 加密,请使用 4096。