元数据加密

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。