元数据加密

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

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

每当启用 FBE 时,可合并的存储设备上总是会启用元数据加密。您还可以在内部存储设备上启用元数据加密。

内部存储设备上的实现

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

前提条件

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

元数据加密要求在内核中启用 dm-default-key 模块。在 Android R 及更高版本中,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

设置元数据文件系统

由于在元数据加密密钥出现之前,用户数据分区中的所有内容均无法读取,因此分区表必须留出一个名为“元数据分区”的单独分区,用于存储保护该密钥的 Keymaster Blob。该元数据分区的大小应为 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 将元数据加密密钥进行硬件封装。 如需详细了解硬件封装的密钥及其前提条件,请参阅 FBE 文档

验证

在 Android R 或更高版本上,首先运行以下命令,验证内部存储设备是否启用了元数据加密,以及元数据加密是否使用了预期设置:

    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
    

如果您启用了 Adiantum 加密,第三个字段就为 xchacha12,aes-adiantum-plain64

此外,请运行如下所述的额外测试,并注意以下常见问题。

常见问题

在调用 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.bp 中添加几行代码,并将 check_encryption.cpp 添加到 platform/system/vold 以测试您的实现。

Android.bp 的更改

下面列出了对 Android.bp 的更改。

...
    }

    cc_binary {
        name: "vold",
        defaults: [
            "vold_default_flags",
            "vold_default_libs",
        ],

        srcs: ["main.cpp"],
        static_libs: ["libvold"],
        product_variables: {
            arc: {
                static_libs: [
                    "arc_services_aidl",
                    "libarcobbvolume",
                ],
            },
        },
        init_rc: [
            "vold.rc",
            "wait_for_keymaster.rc",
        ],

        required: [
            "check_encryption",
            "mke2fs",
            "vold_prepare_subdirs",
            "wait_for_keymaster",
        ],
    }
    ...
    
    }

    cc_binary {
        name: "check_encryption",
        defaults: ["vold_default_flags"],
        srcs: [
            "FileDeviceUtils.cpp",
            "check_encryption.cpp",
        ],
        shared_libs: [
            "libbase",
        ],
    }

添加 check_encryption.cpp

check_encryption.cpp 添加到 platform/system/vold 中。

/*
     * Copyright (C) 2017 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    #include "FileDeviceUtils.h"

    #include <cmath>
    #include <string>

    #include <assert.h>
    #include <stdio.h>

    #include <android-base/file.h>
    #include <android-base/logging.h>
    #include <android-base/unique_fd.h>
    using android::base::unique_fd;
    using android::base::ReadFileToString;
    using android::base::WriteStringToFile;

    namespace android {
    namespace vold {
    const size_t sectorsize = 1 << 12;
    const int sectorcount = 1024;
    static double randomness_score(const std::string& checkme) {
        unsigned int freq[256] = {0};
        unsigned int sum = 256;
        double loglaplace = 0;
        for (auto b : checkme) {
            loglaplace -= 8 + log2(static_cast<double>(++freq[static_cast<uint8_t>(b)]) / (sum++));
        }
        return loglaplace;
        LOG(INFO) << "Score: " << loglaplace;  // if negative, not random
        return loglaplace < 0;
    }
    static bool run_test(const std::string& device) {
        unique_fd device_fd(open(device.c_str(), O_RDONLY | O_CLOEXEC));
        if (device_fd.get() == -1) {
            PLOG(ERROR) << "Failed to open " << device;
            return false;
        }
        int randompassed = 0;
        auto buf = std::string(sectorsize, '\0');
        for (int i = 0; i < sectorcount; i++) {
            auto l = read(device_fd.get(), &buf[0], buf.size());
            if (l < 1) {
                PLOG(ERROR) << "Failed read on sector " << i;
                return false;
            }
            if (((size_t)l) != buf.size()) {
                LOG(ERROR) << "Short read on sector " << i;
                return false;
            }
            auto score = randomness_score(buf);
            if (score >= 0) {
                randompassed++;
                LOG(INFO) << "Passed randomness check on sector " << i << " with score " << score;
            } else {
                LOG(ERROR) << "Failed randomness check on sector " << i << " with score " << score;
            }
        }
        LOG(INFO) << "Passed randomness check on " << randompassed << "/" << sectorcount << " sectors";
        return randompassed == sectorcount;
    }
    }  // namespace vold
    }  // namespace android
    int main(int argc, const char* const argv[]) {
        setenv("ANDROID_LOG_TAGS", "*:v", 1);
        android::base::InitLogging(const_cast<char**>(argv), android::base::StderrLogger);
        if (argc != 2) {
            LOG(ERROR) << "Usage: " << argv[0] << " <device>";
            LOG(ERROR) << "example: " << argv[0] << " /dev/block/bootdevice/by-name/userdata";
            return -1;
        }
        android::vold::run_test(std::string(argv[1]));
        return 0;
    }

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

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

当前方法

在搭载 Android R 或更高版本的设备上,可合并的存储设备上的元数据加密会使用 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。