【数据安全】4. Android 文件级加密(File-based Encryption)之密钥管理

这篇具有很好参考价值的文章主要介绍了【数据安全】4. Android 文件级加密(File-based Encryption)之密钥管理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. FBE 密钥管理简介

在前文《【数据安全】3. Android 文件级加密(File-based Encryption)技术介绍》  中介绍了在HLOS中 FBE 的软件流程,而密钥管理则贯穿于整个流程中。

密钥管理中有以下关键对象:

Encryption Storage Master Key Encryption Policy
System DE Storage System DE Master Key System DE Encryption Policy
User.0 DE Storage User.0 DE Master Key User.0 DE Encryption Policy
User.0 CE Storage User.0 CE  Master Key User.0 CE Encryption Policy

这三者的关系可以用一个例子来说明:

  1. 在 userdata 分区中, 位于 System DE Storage 中的每个文件和文件夹都有自己的 System DE Encryption Policy,被记录到文件的扩展属性中;
  2. System DE Encryption Policy 记录了:
    • 加密使用的 key ,即 System DE Master Key;
    • 文件内容的加密算法;
    • 文件名的加密算法;
  3. 在 I/O 时,底层驱动根据文件或文件夹的 System DE Encryption Policy 加密和解密数据;

System DE Master key、User DE Master key 和 User CE Master key 以及使用这些 key 加解密文件的流程基本一致,它们的主要区别是针对不同安全等级的存储位置而已。但是当用户设置锁屏密码后,User CE Master  key 的认证方式会改变,受用户密码的保护。因此本文只介绍 System DE Master Key 和用户设置锁屏密码后的 User CE Master  Key 。

密钥管理涉及的主要几个模块和相关操作:


1.1 VOLD

  • 向 keymaster TA 请求创建 Master Key ;
  • 将 Master Key 注册到 kernel keyring;
    • I/O 时,read/write 系统调用会进入内核,接着内核通过文件的 Encryption Policy 找到 Master key ,继续加解密文件的数据;
    • 可以通过 ioctl cmd FS_IOC_ADD_ENCRYPTION_KEY 将 Master Key 注册到 kernel keyring;
  • 为不同存储的位置生成加密上下文环境 Encryption Policy;
    • Encryption Policy 包括:
      • Policy 版本
      • Policy flags
      • 文件内容加密算法
      • 文件名加密算法
      • 加解密使用的 Master Key

Encryption Policy 在代码中的定义:

/ /system/extras/libfscrypt/include/fscrypt/fscrypt.h
struct EncryptionOptions {
    int version;
    int contents_mode;
    int filenames_mode;
    int flags;
    bool use_hw_wrapped_key;

    // Ensure that "version" is not valid on creation and so must be explicitly set
    EncryptionOptions() : version(0) {}
};

struct EncryptionPolicy {
    EncryptionOptions options;
    std::string key_raw_ref;
};
// bionic/libc/kernel/uapi/linux/fscrypt.h
struct fscrypt_policy_v1 {
  __u8 version;
  __u8 contents_encryption_mode;
  __u8 filenames_encryption_mode;
  __u8 flags;
  __u8 master_key_descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];
};
#define FSCRYPT_KEY_DESC_PREFIX "fscrypt:"
#define FSCRYPT_KEY_DESC_PREFIX_SIZE 8
#define FSCRYPT_MAX_KEY_SIZE 64
struct fscrypt_key {
  __u32 mode;
  __u8 raw[FSCRYPT_MAX_KEY_SIZE];
  __u32 size;
};
#define FSCRYPT_POLICY_V2 2
#define FSCRYPT_KEY_IDENTIFIER_SIZE 16
struct fscrypt_policy_v2 {
  __u8 version;
  __u8 contents_encryption_mode;
  __u8 filenames_encryption_mode;
  __u8 flags;
  __u8 __reserved[4];
  __u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
};
struct fscrypt_get_policy_ex_arg {
  __u64 policy_size;
  union {
    __u8 version;
    struct fscrypt_policy_v1 v1;
    struct fscrypt_policy_v2 v2;
  } policy;
};

1.2 Keymaster HAL/TA

响应 HLOS 的请求,创建 Master Key 并返回给 HLOS。根据密钥安全原则,Master Key 不允许出现在 HLOS,因此只能加密 Master Key 生成 key blob,再返回给 HLOS

  • 那么加密 Master  Key 的 key 从哪里来?谁维护?
  • 注册到 kernel keyring 的 Master Key 并非本体,后续怎么找到真正的 Master Key?

1.3 文件系统和 Encryption Policy

按照前文介绍,不同安全等级的存储位置,将使用不同的 key 来加解密文件。为了达成这一点,kernel 文件系统层支持设置文件的加密上下文环境,即 Encryption Policy,并保存到文件的扩展属性。在 kernel 文档 Documentation/filesystems/fscrypt.rst 有相关介绍:

Encryption context
------------------

An encryption policy is represented on-disk by struct fscrypt_context_v1 or struct fscrypt_context_v2.  It is up to individual filesystems to decide where to store it, but normally it would be stored in a hidden extended attribute.  It should *not* be exposed by the xattr-related system calls such as getxattr() and setxattr() because of the special semantics of the encryption xattr. (In particular, there would be much confusion if an encryption policy were to be added to or removed from anything other than an empty directory.)  These structs are defined as follows::

    #define FSCRYPT_KEY_IDENTIFIER_SIZE  16
    struct fscrypt_context_v2 {
            u8 version;
            u8 contents_encryption_mode;
            u8 filenames_encryption_mode;
            u8 flags;
            u8 __reserved[4];
            u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
            u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
  };

在 user space,可以使用 ioctl syscall 通过

  • FS_IOC_SET_ENCRYPTION_POLICY : 设置文件的 Encryption Policy,参考代码:fscrypt.cpp;
  • FS_IOC_GET_ENCRYPTION_POLICY_EX 或 FS_IOC_GET_ENCRYPTION_POLICY :获取文件的 Encryption Policy, 参考代码:file_based_encryption_tests.cpp​;

下面展示了分别从 SYSTEM DE,USER DE/CE 存储路径中得到的 Encryption Policy。从 master_key_identifier 字段可以看出它们确实使用不同的 Master Key 来加密。

# ./fbe-get-policy_static /data/misc
[Info]: Detected support for FS_IOC_ADD_ENCRYPTION_KEY
[/data/misc]:
    .version = 2
    .flags   = 0xa
    .contents_encryption_mode  = 1   (AES_256_XTS)
    .filenames_encryption_mode = 4   (AES_256_CTS)
    .master_key_identifier[16] = 599F1AA43727D401198DD6936A1F2060
    >>>> SYSTEM DE Policy <<<<


# ./fbe-get-policy_static /data/system_de/0
[Info]: Detected support for FS_IOC_ADD_ENCRYPTION_KEY
[/data/system_de/0]:
    .version = 2
    .flags   = 0xa
    .contents_encryption_mode  = 1   (AES_256_XTS)
    .filenames_encryption_mode = 4   (AES_256_CTS)
    .master_key_identifier[16] = E0DF36C996839C6F1169863ECF6BBC69
    >>>> USER 0 DE Policy <<<<

# ./fbe-get-policy_static /data/system_ce/0
[Info]: Detected support for FS_IOC_ADD_ENCRYPTION_KEY
[/data/system_ce/0]:
    .version = 2
    .flags   = 0xa
    .contents_encryption_mode  = 1   (AES_256_XTS)
    .filenames_encryption_mode = 4   (AES_256_CTS)
    .master_key_identifier[16] = F676012645AF5CEF378AEF0BF361E821
    >>>> USER 0 CE Policy <<<<

疑问:在用户的设备中,文件系统可能存放着数以百万的文件,需要每个文件一一设置 Encryption Policy?

答案肯定不是。在文件系统内支持 Encryption Policy 递归的继承,即目录下的所有文件以及子目录均继承父目录的 Encryption Policy。因此我们只需要手动为有限的目录设置 Encryption Policy,那么在这些目录下创建文件或者子目录时,在 kernel 文件系统层,驱动会帮助我们自动给这些文件或者子目录设置与父目录一致的 Encryption Policy

这意味着 Android  系统只需要设置下面这些目录的 Encryption Policy 即可。

Unencrypted Storage     System DE Storage     User.0 DE Storage     User.0 CE Storage
/data/lost+found
/data/unencrypted
/data/media
/data/system_ce
/data/system_de
/data/misc_ce
/data/misc_de
/data/user
/data/user_de
/data/vendor_ce
/data/vendor_de
/data/misc
/data/local
/data/vendor
/data/property
/data/tombstones
/data/dalvik-cache
/data/resource-cache
/data/backup
/data/system
/data/cache
/data/adb
/data/anr
/data/app
/data/app-asec
/data/app-ephemeral
/data/app-lib
/data/app-private
/data/bootchart
/data/dpm
/data/drm
/data/mediadrm
/data/nfc
/data/ota
/data/ota_package
/data/ss
/data/system_de/0
/data/misc_de/0
/data/vendor_de/0
/data/user_de/0
/data/system_ce/0
/data/misc_ce/0
/data/vendor_ce/0
/data/media/0
/data/data

疑问:在实际代码实现中,谁在哪里设置的呢?

① System DE 路径的 Encryption Policy 是由 init 进程通过 init.rc 设置。

# system/core/rootdir/init.rc
mkdir /data/bootchart 0755 shell shell encryption=Require
mkdir /data/vendor 0771 root root encryption=Require
mkdir /data/anr 0775 system system encryption=Require
mkdir /data/tombstones 0771 system system encryption=Require
mkdir /data/misc 01771 system misc encryption=Require
mkdir /data/property 0700 root root encryption=Require
mkdir /data/apex 0755 root system encryption=None
mkdir /data/apex/decompressed 0755 root system encryption=Require
mkdir /data/app-staging 0751 system system encryption=DeleteIfNecessary
mkdir /data/apex/ota_reserved 0700 root system encryption=Require
mkdir /data/local 0751 root root encryption=Require
mkdir /data/preloads 0775 system system encryption=None
mkdir /data/app-private 0771 system system encryption=Require
mkdir /data/app-ephemeral 0771 system system encryption=Require
......

② User DE 和 User CE 路径的 Encryption Policy 是由 vold 进程直接设置。

// system/vold/FsCrypt.cpp
bool fscrypt_prepare_user_storage(const std::string& volume_uuid, userid_t user_id, int serial,
                                  int flags) {
    if (flags & android::os::IVold::STORAGE_FLAG_DE) {
        // DE_sys key
        auto system_legacy_path = android::vold::BuildDataSystemLegacyPath(user_id); /* /data/system/users/<user_id> */
        auto misc_legacy_path = android::vold::BuildDataMiscLegacyPath(user_id); /* /data/misc/user/<user_id> */
        auto profiles_de_path = android::vold::BuildDataProfilesDePath(user_id); /* /data/misc/profiles/cur/<user_id> */

        // DE_n key
        auto system_de_path = android::vold::BuildDataSystemDePath(user_id); /* /data/system_de/<user_id> */
        auto misc_de_path = android::vold::BuildDataMiscDePath(user_id); /* /data/misc_de/<user_id> */
        auto vendor_de_path = android::vold::BuildDataVendorDePath(user_id); /* /data/vendor_de/<user_id> */
        auto user_de_path = android::vold::BuildDataUserDePath(volume_uuid, user_id); /* /data/user_de/<user_id> */

        if (fscrypt_is_native()) {
            EncryptionPolicy de_policy;
            if (volume_uuid.empty()) {
                if (!lookup_policy(s_de_policies, user_id, &de_policy)) return false;
                if (!EnsurePolicy(de_policy, system_de_path)) return false;
                if (!EnsurePolicy(de_policy, misc_de_path)) return false;
                if (!EnsurePolicy(de_policy, vendor_de_path)) return false;
            } else {
                if (!read_or_create_volkey(misc_de_path, volume_uuid, &de_policy)) return false;
            }
            if (!EnsurePolicy(de_policy, user_de_path)) return false;
        }
    }

    if (flags & android::os::IVold::STORAGE_FLAG_CE) {
        // CE_n key
        auto system_ce_path = android::vold::BuildDataSystemCePath(user_id); /* /data/system_ce/<user_id> */
        auto misc_ce_path = android::vold::BuildDataMiscCePath(user_id); /* /data/misc_ce/<user_id> */
        auto vendor_ce_path = android::vold::BuildDataVendorCePath(user_id); /* /data/vendor_ce/<user_id> */
        auto media_ce_path = android::vold::BuildDataMediaCePath(volume_uuid, user_id); /* /data/media/<user_id> */
        auto user_ce_path = android::vold::BuildDataUserCePath(volume_uuid, user_id); /* /data/user/<user_id> */

        if (fscrypt_is_native()) {
            EncryptionPolicy ce_policy;
            if (volume_uuid.empty()) {
                if (!lookup_policy(s_ce_policies, user_id, &ce_policy)) return false;
                if (!EnsurePolicy(ce_policy, system_ce_path)) return false;
                if (!EnsurePolicy(ce_policy, misc_ce_path)) return false;
                if (!EnsurePolicy(ce_policy, vendor_ce_path)) return false;
            } else {
                if (!read_or_create_volkey(misc_ce_path, volume_uuid, &ce_policy)) return false;
            }
            if (!EnsurePolicy(ce_policy, media_ce_path)) return false;
            if (!EnsurePolicy(ce_policy, user_ce_path)) return false;
        }
    }

    return true;
}

疑问:Encryption Policy 中 master_key_identifier 字段的意义是什么?怎么得到的?

master_key_identifier 即表示使用哪个 Master,至于它怎么来的?怎么根据他找到 Master Key,详见下文。


1.4 Keyring

在 I/O 时,可以通过文件系统,从文件的扩展属性中存储的 Encryption Policy 知道使用哪 Master Key 来加解密文件,即 Encryption Policy 的 master_key_identifier 字段。但这仅仅是 Master  Key  的一个索引,那实际的 Master Key 存在哪里呢?在 FBE 的实现中,这个 Master Key 被存储在 kernel keyring 中,可以通过 master_key_identifier 从 kernel keyring 中找到这个 key。

这里需要注意一点,在使能 wrapped key feture 时,Master Key 不会在 HLOS 中以明文的形式存在,更不会出现在 kernel keyring 中 ,在 keyring 中的实际是 Master Key 的 wrapped key,下文直接称 Wrapped Key。关于 Wrapped key 和 Master Key 的关系,下文会详细介绍。

疑问:Keyring 是一个什么样的存在?

参考内核文档:Documentation/security/keys/core.rst。key 和 keyring 在内核中都是以结构体 struct key (include/linux/key.h)表示。

每个 key 和 keyring 都有这些属性:

  • serial number
  • key type
  • key description (for matching a key in a search)
  • access control information
  • expiry time
  • payload
  • State

可以看到 struct key 自身就有权限控制机制,这为 key 安全使用提供了保障。我们重点关注 3 个属性 key type、key description、payload。

key type

key type 在内核中由 struct key_type 表示。定义了一些可以对该类型的 key 执行的操作函数。

key description

一个可打印的字符串。key type 需要提供了一个操作,在 key 和标准字符串之间进行匹配。也就是可以根据 key description 找到 key。

payload

即 struct key 的有效负荷:

  • 它可以是一个真正的 “key”,那 struct key 表示的就是一个 key;
  • 也可以是 keys 的链表,那 struct key 表示的就是 keyring,其他 key 或 keyring 可以链接到它上面;
  • 当然 payload 并非一定需要存在;

Master Key 被注册到 Keyring 后,它的 key description 中包含了 master_key_identifier ,那也就是说我们可以根据 Encryption Policy 中的 master_key_identifier 找到 Master Key。查找的方法可以从下面代码看出。

// fs/crypto/keyring.c
/*
 * Find the specified master key in ->s_master_keys.
 * Returns ERR_PTR(-ENOKEY) if not found.
 */
struct key *fscrypt_find_master_key(struct super_block *sb,
				    const struct fscrypt_key_specifier *mk_spec)
{
	struct key *keyring;
	char description[FSCRYPT_MK_DESCRIPTION_SIZE];

	/*
	 * Pairs with the smp_store_release() in allocate_filesystem_keyring().
	 * I.e., another task can publish ->s_master_keys concurrently,
	 * executing a RELEASE barrier.  We need to use smp_load_acquire() here
	 * to safely ACQUIRE the memory the other task published.
	 */
	keyring = smp_load_acquire(&sb->s_master_keys);
	if (keyring == NULL)
		return ERR_PTR(-ENOKEY); /* No keyring yet, so no keys yet. */

	format_mk_description(description, mk_spec);
	return search_fscrypt_keyring(keyring, &key_type_fscrypt, description);
}

/* Search ->s_master_keys or ->mk_users */
static struct key *search_fscrypt_keyring(struct key *keyring,
					  struct key_type *type,
					  const char *description)
{
	/*
	 * We need to mark the keyring reference as "possessed" so that we
	 * acquire permission to search it, via the KEY_POS_SEARCH permission.
	 */
	key_ref_t keyref = make_key_ref(keyring, true /* possessed */);

	keyref = keyring_search(keyref, type, description, false);
	if (IS_ERR(keyref)) {
		if (PTR_ERR(keyref) == -EAGAIN || /* not found */
		    PTR_ERR(keyref) == -EKEYREVOKED) /* recently invalidated */
			keyref = ERR_PTR(-ENOKEY);
		return ERR_CAST(keyref);
	}
	return key_ref_to_ptr(keyref);
}
// include/uapi/linux/fscrypt.h
struct fscrypt_policy_v2 {
	__u8 version;
	__u8 contents_encryption_mode;
	__u8 filenames_encryption_mode;
	__u8 flags;
	__u8 __reserved[4];
	__u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
};

/*
 * Specifies a key, either for v1 or v2 policies.  This doesn't contain the
 * actual key itself; this is just the "name" of the key.
 */
struct fscrypt_key_specifier {
	__u32 type;	/* one of FSCRYPT_KEY_SPEC_TYPE_* */
	__u32 __reserved;
	union {
		__u8 __reserved[32]; /* reserve some extra space */
		__u8 descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];
		__u8 identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
	} u;
};

通过

  • fscrypt_find_master_key(struct super_block *sb, const struct fscrypt_key_specifier *mk_spec) 类型为 fscrypt_key_specifier 的参数 mk_spec;
  • fscrypt_key_specifier 的 identifier字段;
  • fscrypt_policy_v2  的 master_key_identifier 字段;

可以很好理解通过 master_key_identifier 找到 Master Key。


2. System DE Master Key

2.1 创建 System DE Master Key 流程

下图展示了在设备第一次启动时,创建 System DE Master Key 的完整流程,在这个章节会对其拆分,分别介绍。

android fbe,Android Security,android,linux,系统安全,安全,安全架构
System DE Master Key 创建流程

2.1.1 创建 System DE Master Key

在 vold 函数 fscrypt_initialize_systemwide_keys 中,Vold 通过 keymaster HAL 向 keymaster TA 请求创建 Master Key。在请求参数中, km::TAG_STORAGE_KEY 表明了创建一个用于存储器加密的 Master Key,HLOS 将这个 key 用作 System DE Master Key。

android fbe,Android Security,android,linux,系统安全,安全,安全架构

这里要注意 keymaster TA 不会直接将 Master Key 的明文直接返回到 HLOS,而是 Master Key Blob。那么 Master Key Blob 是怎么生成的?

  1. KM TA 首选创建一个 AES 算法加密 key,作为 Master Key;
  2. 使用由 SHK 派生出来的 KEK 将 Master Key 加密,并把密文存放到特殊数据结构,即 key blob;
  3. 再使用由 SHK 派生出来的 HMAC Key 通过 HMAC 算法对 key blob 签名;
  4. 这样就生成了 Master Key Blob,返回到 HLOS 的 Vold;

疑问:SHK 是什么?KEK 是什么?HMAC Key 是什么?

① SHK 是设备使能 secure boot 后,生成的一个每个设备唯一、软件或者固件无法导出的 key,TZ 可以从 SHK 派生出各种用途的 key。

② KEK 派生自 SHK ,用于加密 KM TA 生成的 key,因为 key 不允许暴露在 HLOS:

  • 当 HLOS 请求 KM TA 生成 key 时,可以使用 KEK 将 key 加密成 key blob 后返回给 HLOS;
  • key 只能在 secure world 下使用(包括:加密、解密、签名、校验等操作)。因此时 HLOS 有需求时,请求 KM TA 执行相关操作,需要将数据和 key blob 一起传给 KM TA,KM TA 使用 KEK 解密 key blob 得到 key,再对数据执行相关的操作;

③ HMAC Key 派生自 SHK ,用于对 key blob 签名,保证了  key blob 不被恶意篡改或者检查是否损坏;

疑问:为什么 TZ 要将 key 加密后返回 HLOS 呢?

在实际用户场景中,各种各样的进程可能会创建几十个甚至几百个用于各种各样的任务的 key,加密后返回 HLOS,由使用者自行管理,那么 TZ 就无需维护较大的存储区域以及这些 key 与客户端的联系。


2.1.2 HLOS 把 System DE Master Key Blob 加密后保存到文件系统

在上一个步骤中,KM TA 创建了 Master Key ( km::TAG_STORAGE_KEY FBE),并以 key blob 的形式返回到 HLOS。在 vold 收到 Master Key Blob 后,又将这个 key blob 加密后保存到文件系统中。

android fbe,Android Security,android,linux,系统安全,安全,安全架构
创建  KSK 和加密 Master Key 流程图

疑问:Master Key Blob 已经是密文了,为什么 vold 还要将 key blob 二次加密?

这个操作似乎有点多余,如果对 key 加密一次不安全,那么加密两次也不见得更安全,或者为什么不加密更多次呢?

这里我们要从 FBE 的设计来看,Google 把 userdata 分区划分了不同安全等级的存储位置,但仅使用不同的密钥并不能体现出安全等级这个概念,而是要从对密钥的约束来体现,比如经过什么样的认证后,哪些位置的数据才允许访问,言下之意就是对应的加密密钥才允许被使用。那怎么认证呢?

System DE Master Key 的安全认证正是通过二次加密来体现的,但是加密 Master Key 的 key (KEK) 怎么做到这一点呢?System DE 的设计初衷就是相应的存储位置是设备绑定,即这些存储位置的数据只能在特定的设备(CPU、存储器绑定)上才能解密和访问。Master Key 只负责用户加解密文件的数据,但是特定的设备怎么保证呢?

这就是加密 Master Key 的 key 要达成的目标,我们把这个 key 称作 KSK(Key Strorage Key,这不是专有名词,而是按照 Google 代码变量名来命名 )。

从流程图中可以看出,在创建 KSK 时需要指定 app id(即表明谁创建的?)。同样后续使用这个 key 时,也必须指定相同的 appid。appid 的组成中包括设备绑定的信息(每台设备,KSK 的 appid 不一样,因为从它的创建流程可以看出,包括两个随机数 Secdiscardable 和 storage_binding_info.seed)。不仅如此,在 KM TA 内部还会将 secure boot 状态、设备锁状态、安全补丁日期等等信息和 KSK 绑定,在使用 KSK 之前,会校验相关的信息是否严格匹配,只有完全满足后,KSK 才允许用于解密 Master Blob。这些约束的实现都是在 KM TA 完成的。

这里举几个实际的例子说明:

例1:把一台设备的存储器所有数据,dump 到另外一台完全一致的设备上,用户数据为什么无法解密?

        因为 KSK 已经和 secure boot 状态绑定,在另外一台设备上,无法知道 EKE,进而无法解密 KSK blob,KSK 即无法被使用。当然被 KSK 加密的 master key blob 也无法解密,自然获取不到 master key 。实际实现不只如此简单,这里不做详细介绍。

例2:同一台设备,如果 OTA 更新到安全补丁日期升级的软件后,再回滚到旧软件版本,用户数据为什么无法解密?

        因为 KSK 已经和安全补丁日期绑定,安全补丁日期不能减小。安全补丁日期回滚后,KSK 将不可用。

例3:设备第一次开机是在设备锁(也叫 fastboot锁)锁定的情况下开机,那么 unlock 设备锁后,用户数据为什么无法解密?

        因为 KSK 和设备锁的状态绑定,如果设备锁的状态出现反转,KSK 将不可用。

疑问:加密 Master Key Blob 的 key KSK 从何而来 ?

和 Master Key 类似,都是由 KM TA 创建的,并以 key blob 的形式返回 HLOS,由 vold 自行管理。这里注意创建 key 时,参数 km::AuthorizationSet 不一样,这就导致在 KM TA 内部,它们的管理和用途都是不一样。

vold 请求 KM TA 创建 KSK 所使用的 appid的参数、KSK Blob、加密后的 Master Key Blob 被存储到 /data/unencrypted/key/:

# ls /data/unencrypted/key/ -l
total 16
-rw------- 1 root root   268 1970-01-03 04:47 encrypted_key
-rw------- 1 root root   194 1970-01-03 04:47 keymaster_key_blob
-rw------- 1 root root 16384 1970-01-03 04:47 secdiscardable
-rw------- 1 root root    10 1970-01-03 04:47 stretching
-rw------- 1 root root     1 1970-01-03 04:47 version

疑问:第一次开机时,为什么要把 KSK blob  和 加密后的 Master Key Blob 保存到文件系统?

后续每次开机,可以直接通过 KSK blob (/data/unencrypted/key/keymaster_key_blob)解密Master Key Blob 的密文(/data/unencrypted/keyencrypted_key)得到 Master Key Blob,这个动作是在 TZ 内部完成的,有了 Master Key Blob,那么可以将 Master Key 安装到 kernel keyring,详见下文。


2.1.3 安装 Master Key 到 kernel keyring

在上一步骤中,已经成功创建了 Master Key,那接下来就是要把 Master Key 注册到 kernel keyring。在文件 I/O 时可以通过  Encryption Policy 的 master_key_identifier 字段从 kernel keyring 中找到 Master Key。

在这个步骤中的两个重点操作:

  1. 安装 Master Key 到 kernel keyring;
  2. 生成 Master Key 的 master_key_identifier;

软件流程图如下所示:

android fbe,Android Security,android,linux,系统安全,安全,安全架构
安装 master key 和生成 master_key_identifier

流程图比较复杂,接下来根据疑问来分解图中的步骤。

疑问: Master Key 不是不允许暴露在 HLOS 吗?那注册到 kernel keyring 的 Master Key 从何而来?

实际上是 Master Key 的 Wrapped Key 被注册到 kernel keyring ,毕竟 Master Key 确实不能暴露在 HLOS。那 Wrapped Key 从何而来?

android fbe,Android Security,android,linux,系统安全,安全,安全架构
生成 Master key 的 ​​​​​​Wrapped Key 并注册

 从图中可知,生成 Wrapped Key 的过程:

  1. vold 把 Master Key Blob 传送到 KM TA,请求创建 Wrapped Key;
  2. KM TA 使用 HMAC Key 校验 Master Key Blob ;
  3. KM TA 使用 KEK 解密 Master Key Blob 得到 Master Key;
  4. KM TA 使用 Ephemeral Key 通过 AES Wrap Key RFC3394 Algorithm 生成 Master Key 的 Wrapped Key;
    1. Ephemeral Key:从 SHK  派生而来,但是每次设备重启(cold reboot)都会改变,意味着 Wrapped Key 每次设置重启都不一样;
    2. AES Wrap Key RFC3394 Algorithm:详见 Advanced Encryption Standard (AES) Key Wrap Algorithm
  5. KM TA 将生成的 Wrapped Key 返回 HLOS vold;
  6. Vold 通过系统调用 ioctl  FS_IOC_ADD_ENCRYPTION_KEY,将 Wrapped Key 安装到 Kernel Keyring
  • 相反,KM TA 可以使用 Ephemeral Key 通过 AES Unwrap Key RFC3394 Algorithm  将 Wrapped Key 转成 Master Key。因此后面 HLOS 再软件流程中可以把  Wrapped Key 当作 Master Key,在实际执行操作时,KM TA 会将 Wrapped Key 自动转换成 Master Key 后再执行;
  • AES Wrap Key RFC3394 Algorithm 是什么?可以简单的理解为 AES 算法是专门用于数据加解密,而 AES Wrap Key RFC3394 Algorithm 专门用于加解密 AES Key;

疑问:master_key_identifier 是怎么产生的?怎么通过 master_key_identifier 找到 Master Key?

首先给出一张软件流程图,可以看到 master_key_identifier  是在安装 Wrapped Key 的过程中生成的,并且随系统调用返回到 vold 进程。

android fbe,Android Security,android,linux,系统安全,安全,安全架构
生成 master_key_identifier

 生成 master_key_identifier  的步骤有:

  1. Kernel 函数 fscrypt_derive_raw_secret 将 Wrapped Key 发送到 TZ KM TA,请求 Master Key 派生 raw secret ;
  2. KM TA 接收到请求后,使用 Ephemeral Key 通过 Unwrap Wrapped Key 得到 Master Key;
  3. KM TA 通过 KDF ,使得 Master Key 派生出一个 key,并将这个派生 key 返回 HLOS Linux kernel
  4. Linux Kernel 使用从 TZ 返回的派生 key 作为 KDF key,继续通过 KDF 派生出一个 key,这个 key 作为 master_key_identifier;

这里截取了一些在 ioctl 中生成 master_key_identifier 的相关代码:

https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:system/vold/KeyUtil.cpphttps://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:system/vold/KeyUtil.cpp

// system/vold/FsCrypt.cpp
static bool install_storage_key(const std::string& mountpoint, const EncryptionOptions& options,
                                const KeyBuffer& key, EncryptionPolicy* policy) {
    KeyBuffer ephemeral_wrapped_key;
    if (options.use_hw_wrapped_key) {
        if (!exportWrappedStorageKey(key, &ephemeral_wrapped_key)) {
            LOG(ERROR) << "Failed to get ephemeral wrapped key";
            return false;
        }
    }
    return installKey(mountpoint, options, options.use_hw_wrapped_key ? ephemeral_wrapped_key : key,
                      policy);
}

// system/vold/KeyUtil.cpp
bool installKey(const std::string& mountpoint, const EncryptionOptions& options,
                const KeyBuffer& key, EncryptionPolicy* policy) {
    policy->options = options;
    // Put the fscrypt_add_key_arg in an automatically-zeroing buffer, since we
    // have to copy the raw key into it.
    KeyBuffer arg_buf(sizeof(struct fscrypt_add_key_arg) + key.size(), 0);
    struct fscrypt_add_key_arg* arg = (struct fscrypt_add_key_arg*)arg_buf.data();

    // Initialize the "key specifier", which is like a name for the key.
    switch (options.version) {
        case 2:
            // A key for a v2 policy is specified by an 16-byte "identifier",
            // which is a cryptographic hash of the key itself which the kernel
            // computes and returns.  Any user-provided value is ignored; we
            // just need to set the specifier type to indicate that we're adding
            // this type of key.
            arg->key_spec.type = FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER;
            break;
    }

    arg->raw_size = key.size();
    memcpy(arg->raw, key.data(), key.size());

    if (!installFsKeyringKey(mountpoint, options, arg)) return false;

    if (arg->key_spec.type == FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER) {
        // Retrieve the key identifier that the kernel computed.
        policy->key_raw_ref =
                std::string((char*)arg->key_spec.u.identifier, FSCRYPT_KEY_IDENTIFIER_SIZE);
    }
    std::string ref = keyrefstring(policy->key_raw_ref);
    LOG(DEBUG) << "Installed fscrypt key with ref " << ref << " to " << mountpoint;

    if (!installProvisioningKey(key, ref, arg->key_spec)) return false;
    return true;
}

 kernel 从函数  int fscrypt_ioctl_add_key(struct file *filp, void __user *_uarg) 开始:

// fs/crypto/keyring.c
int fscrypt_ioctl_add_key(struct file *filp, void __user *_uarg) {
	struct super_block *sb = file_inode(filp)->i_sb;
	struct fscrypt_add_key_arg __user *uarg = _uarg;
	struct fscrypt_add_key_arg arg;
	struct fscrypt_master_key_secret secret;

    copy_from_user(&arg, uarg, sizeof(arg)

    memset(&secret, 0, sizeof(secret))

    secret.is_hw_wrapped = true

    secret.size = arg.raw_size
    copy_from_user(secret.raw, uarg->raw, secret.size)

    add_master_key(sb, &secret, &arg.key_spec)

	if (arg.key_spec.type == FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER &&
	    copy_to_user(uarg->key_spec.u.identifier, arg.key_spec.u.identifier,
			 FSCRYPT_KEY_IDENTIFIER_SIZE))
}

/* Size of software "secret" derived from hardware-wrapped key */
#define RAW_SECRET_SIZE 32

static int add_master_key(struct super_block *sb,
			  struct fscrypt_master_key_secret *secret,
			  struct fscrypt_key_specifier *key_spec)
{
	int err;

	if (key_spec->type == FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER) {
		u8 _kdf_key[RAW_SECRET_SIZE];
		u8 *kdf_key = secret->raw;
		unsigned int kdf_key_size = secret->size;

		if (secret->is_hw_wrapped) {
			kdf_key = _kdf_key;
			kdf_key_size = RAW_SECRET_SIZE;
			err = fscrypt_derive_raw_secret(sb, secret->raw,
							secret->size,
							kdf_key, kdf_key_size);
			if (err)
				return err;
		}
		err = fscrypt_init_hkdf(&secret->hkdf, kdf_key, kdf_key_size);
		/*
		 * Now that the HKDF context is initialized, the raw HKDF key is
		 * no longer needed.
		 */
		memzero_explicit(kdf_key, kdf_key_size);
		if (err)
			return err;

		/* Calculate the key identifier */
		err = fscrypt_hkdf_expand(&secret->hkdf,
					  HKDF_CONTEXT_KEY_IDENTIFIER, NULL, 0,
					  key_spec->u.identifier,
					  FSCRYPT_KEY_IDENTIFIER_SIZE);
		if (err)
			return err;
	}
	return do_add_master_key(sb, secret, key_spec);
}

add_master_key 的实现中,3个重要函数:

  • fscrypt_derive_raw_secret:通过 Wrapped Key 向 KM TA 请求从Master Key 派生出 KDF Key;
  • fscrypt_init_hkdf:初始化 KDF 函数,设置 KDF Key;
  • fscrypt_hkdf_expand:通过 KDF ,从 KDF Key 派生出新的 key,作为 master_key_identifier ;

从上图中可以看到,当 ioctl 返回时,master_key_identifier 也会随之返回,最终保存到文件 /data/unencrypted/ref 文件。

疑问:System DE Master Key 的 master_key_identifier 为什么被保存到 /data/unencrypted/ref (其他 User DE/CE 的 master_key_identifier 并不会被保存到文件)?

因为创建和产生 master_key_identifier 的 流程是在 vold 进程中,而创建 System Device Encrypted (DE) Storage 并给目录设置 Encryption Policy 是 init 进程通过 init.rc 完成的。Google 通过文件存储的方式,让 init 可以得到  System DE Master Key 的 master_key_identifier。

疑问:Wrapped Key 是怎么被安装到 keyring 上的?

这就进入到函数 do_add_master_key 中,观察参数列表:

  • sb :和分区有关,意味这这些 key 只允许用于所指分区;
  • secret:记录着 vold 注册的  Wrapped Key
  • mk_spec: 记录着 Master Key 的 master_key_identifier
// fs/crypto/keyring.c
static int do_add_master_key(struct super_block *sb,
			     struct fscrypt_master_key_secret *secret,
			     const struct fscrypt_key_specifier *mk_spec)

到这里,可以先返回上文 1.4 Keyring 章节再次回顾以下 keyring 相关的属性再继续。

直接看 do_add_master_key 执行结果最终示意图(下图是 add 两个 Master Key 的示意图):

android fbe,Android Security,android,linux,系统安全,安全,安全架构
 Install Master Key to Keyring

① Userdata 分区 superblock 中,指针 s_master_keys 指向一个 key_type_keyring 类型的 keyring;

②  类型为 key_type_keyring 类型的 keyring

我们将其称为 master keys keyring。从代码中可以看出,该 keyring 的 description 中包括了分区的 Informational name。

// /fs/crypto/keyring.c
static void format_fs_keyring_description(
			char description[FSCRYPT_FS_KEYRING_DESCRIPTION_SIZE],
			const struct super_block *sb)
{
	sprintf(description, "fscrypt-%s", sb->s_id);
}

可以在 /proc/keys 节点看到该 keyring 的信息:

# cat /proc/keys

0baa3b23 I------     1 perm 080b0000     0     0 keyring   fscrypt-dm-8: 6

我们可以将 Userdata 有关的所有 Master Key 链接到该 keyring上,正如上图所示,链接了 2 个 Master Key。

③ 链接到 master keys keyring 上的一个类型为 key_type_fscrypt 的 key

这个 key 的 description 如下所示,可以看到 description 就是 master_key_identifier,意味着后续我们可以根据  master_key_identifier 从 keyring s_master_keys 上找到它。

static void format_mk_description(
			char description[FSCRYPT_MK_DESCRIPTION_SIZE],
			const struct fscrypt_key_specifier *mk_spec)
{
	sprintf(description, "%*phN",
		master_key_spec_len(mk_spec), (u8 *)&mk_spec->u);
}

可以在 /proc/keys 节点看到该 key 的信息:

# cat /proc/keys

26066299 I------  1214 perm 08090000     0     0 ._fscrypt f676012645af5cef378aef0bf361e821

从上图可以看到这个 key 的 payload 指向 struct fscrypt_master_key,里面存储着 Master Key(严格讲是 Wrapped Key);

④ struct fscrypt_master_key,包含三个重要成员:

  • mk_spec:包含 master_key_identifier ;
  • mk_secret:包含 Master Key 的 Wrapped Key,用于后续加解密文件数据。
  • mk_users:这是指针,指向一个 key_type_keyring 的 keyring;

⑤ mk_users 指向的类型为 key_type_keyring 的 keyring

我们称其为 mk_users keyring。

在多用户场景下,所有安装和共享此 Master Key 的用户,都分别创建一个类型 key_type_fscrypt_user 的 key 链接到 mk_users keyring。作用:跟踪有哪些用户使用此 Master Key,只有所有用户都 remove 此 Master Key,才允许真正 remove 该 Master Key。

mk_users keyring 的 description 如下表示:

static void format_mk_users_keyring_description(
			char description[FSCRYPT_MK_USERS_DESCRIPTION_SIZE],
			const u8 mk_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE])
{
	sprintf(description, "fscrypt-%*phN-users",
		FSCRYPT_KEY_IDENTIFIER_SIZE, mk_identifier);
}

 可以在 /proc/keys 节点看到该 keyring 的信息:

# cat /proc/keys

0a4c3a54 I------     1 perm 080b0000     0     0 keyring   fscrypt-f676012645af5cef378aef0bf361e821-users: 1

⑥ 类型为 key_type_fscrypt_user 的 key

被链接到 mk_users keyring 上,表明哪个用户正在使用此 Master Key。key 的 description 如下表示:

static void format_mk_user_description(
			char description[FSCRYPT_MK_USER_DESCRIPTION_SIZE],
			const u8 mk_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE])
{

	sprintf(description, "%*phN.uid.%u", FSCRYPT_KEY_IDENTIFIER_SIZE,
		mk_identifier, __kuid_val(current_fsuid()));
}

注意看代码,uid 是该 key 的 description 一部分。在 /proc/keys 节点中看到该 key 的信息:

# cat /proc/keys

2f06f609 I--Q---     1 perm 08010000     0     0 .fscrypt  f676012645af5cef378aef0bf361e821.uid.0

总结,在执行完  do_add_master_key 函数后:

  • 可以通过 master_key_identifier 从分区 super block 结构中 s_master_keys 指向的 keyring 中找到 Master Key;
  • 在多用户场景中,所有使用 Master Key 的用户 remove 了Master Key 后,才允许真正 remove Master Key;

2.2 非第一次启动,vold 加载和安装 System DE Master Key

在第一次设备启动时, vold 创建了 System DE Master Key,并将 key 相关的信息存储到文件系统:

# ls /data/unencrypted/key/ -l
total 16
-rw------- 1 root root   268 1970-01-03 04:47 encrypted_key
-rw------- 1 root root   194 1970-01-03 04:47 keymaster_key_blob
-rw------- 1 root root 16384 1970-01-03 04:47 secdiscardable
-rw------- 1 root root    10 1970-01-03 04:47 stretching
-rw------- 1 root root     1 1970-01-03 04:47 version

在后续每次启动时,vold 加载 /data/unencrypted/key/ 目录下的文件,根据下面图示流程,即可得到 System DE Master Key 的 Wrapped Key,并将其安装到 kernel keyring。

android fbe,Android Security,android,linux,系统安全,安全,安全架构
非第一次启动, vold 加载和安装 System DE Master Key

2.3 创建和校验 System DE Storage

到此密钥准备已经完成:

  • Master Key (严格将应该是 Master Key 的 Wrapped Key)已经安装到 kernel keyring。
  • kernel  将 Master Key 的 master_key_identifier  返回 vold,并且  vold 将其保存到 /data/unencrypted/ref 

接下来就是在 init.rc 中为 System DE Storage 的目录设置 Encryption Policy。


# system/core/rootdir/init.rc
mkdir /data/bootchart 0755 shell shell encryption=Require
mkdir /data/vendor 0771 root root encryption=Require
mkdir /data/anr 0775 system system encryption=Require
mkdir /data/tombstones 0771 system system encryption=Require
mkdir /data/misc 01771 system misc encryption=Require
mkdir /data/property 0700 root root encryption=Require
mkdir /data/apex 0755 root system encryption=None
mkdir /data/apex/decompressed 0755 root system encryption=Require
mkdir /data/app-staging 0751 system system encryption=DeleteIfNecessary
mkdir /data/apex/ota_reserved 0700 root system encryption=Require
mkdir /data/local 0751 root root encryption=Require
mkdir /data/preloads 0775 system system encryption=None
mkdir /data/app-private 0771 system system encryption=Require
mkdir /data/app-ephemeral 0771 system system encryption=Require

注:一定得是在 init.rc 中创建 System DE Storage,因为 init.rc 中的  mkdir 指令被封装,不仅支持创建文件夹,还会设置或校验 Encryption Policy。

 当执行 init.rc 中 mkdir 指令时,mkdir 先确认文件夹已经存在,接着设置或校验 Encryption Policy:

  • mkdir 会首先从 /data/unencrypted/ref 中加载目标 Encryption Policy;
  • 接着 mkdir 设置文件夹 Encryption Policy :
    • 如果检测到文件夹不存在 Encryption Policy :
      • 如果文件夹为空,则直接通过 ioctl 设置 Encryption Policy,返回 ioctl 的结果;
      • 如果文件夹非空,则直接返回失败;
    • 如果文件夹已经设置 Encryption Policy,则读取文件夹的 Encryption Policy,对比是否和 /data/unencrypted/ref 记录的 Encryption Policy 一致,返回结果;

前文提到过,如果 mkdir 设置 Encryption Policy 失败,那么会自动 reboot 到 recovery,强制格式化 userdata 分区。这样安全性很高,但是一旦出现问题,代价也很高,会造成用户数据丢失。

比如我们创建一个保存 log 的文件夹 /data/log,从安全的角度来讲,该文件夹的内容需要加密,但是对用户来说,log 数据并不重要。在上述的流程中,这个文件夹的 Encryption Policy 一旦异常,则导致用户数据全部丢失,是非常不合理的。为了解决类似问题,在 Android 11 中,Google 在 mkdir 中引入参数 'encryption',可以更灵活的控制不同文件夹的 Encryption Policy 设置和校验策略:

  • encryption=Require :强制设置和校验,必须严格匹配;
  • encryption=None :不设置或不校验,即文件夹不加密;
  • encryption=Attempt:尝试设置或校验,即使失败,也不做处理;
  • encryption=DeleteIfNecessary:尝试设置和校验,如果失败了,清空文件夹,再次强制设置和校验;
  • mkdir 中如果不指定 encryption 参数,则行为与 encryption=Require 一致;

对于上述的 /data/log 文件夹的处理,我们就可以使用:

mkdir /data/log 0771 system system encryption=Attempt

或者

mkdir /data/log 0771 system system encryption=DeleteIfNecessary

下面是 mkdir 的代码实现截取,详见 system/core/init/builtins.cpp。https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:system/core/init/builtins.cpp;bpv=0;bpt=1

const BuiltinFunctionMap& GetBuiltinFunctionMap() {
    constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
    // clang-format off
    static const BuiltinFunctionMap builtin_functions = {
        {"mkdir",                   {1,     6,    {true,   do_mkdir}}},
        // TODO: Do mount operations in vendor_init.
    }
}

// mkdir <path> [mode] [owner] [group] [<option> ...]
static Result<void> do_mkdir(const BuiltinArguments& args) {
    auto options = ParseMkdir(args.args);
    if (!options.ok()) return options.error();
    return make_dir_with_options(*options);
}


static Result<void> make_dir_with_options(const MkdirOptions& options) {
    std::string ref_basename;
    if (options.ref_option == "ref") {
        ref_basename = fscrypt_key_ref;
    } else if (options.ref_option == "per_boot_ref") {
        ref_basename = fscrypt_key_per_boot_ref;
    } else {
        return Error() << "Unknown key option: '" << options.ref_option << "'";
    }

    struct stat mstat;
    if (lstat(options.target.c_str(), &mstat) != 0) {
        if (errno != ENOENT) {
            return ErrnoError() << "lstat() failed on " << options.target;
        }
        if (!make_dir(options.target, options.mode)) {
            return ErrnoErrorIgnoreEnoent() << "mkdir() failed on " << options.target;
        }
        if (lstat(options.target.c_str(), &mstat) != 0) {
            return ErrnoError() << "lstat() failed on new " << options.target;
        }
    }
    if (!S_ISDIR(mstat.st_mode)) {
        return Error() << "Not a directory on " << options.target;
    }
    bool needs_chmod = (mstat.st_mode & ~S_IFMT) != options.mode;
    if ((options.uid != static_cast<uid_t>(-1) && options.uid != mstat.st_uid) ||
        (options.gid != static_cast<gid_t>(-1) && options.gid != mstat.st_gid)) {
        if (lchown(options.target.c_str(), options.uid, options.gid) == -1) {
            return ErrnoError() << "lchown failed on " << options.target;
        }
        // chown may have cleared S_ISUID and S_ISGID, chmod again
        needs_chmod = true;
    }
    if (needs_chmod) {
        if (fchmodat(AT_FDCWD, options.target.c_str(), options.mode, AT_SYMLINK_NOFOLLOW) == -1) {
            return ErrnoError() << "fchmodat() failed on " << options.target;
        }
    }
    if (IsFbeEnabled()) {
        if (!FscryptSetDirectoryPolicy(ref_basename, options.fscrypt_action, options.target)) {
            return reboot_into_recovery(
                    {"--prompt_and_wipe_data", "--reason=set_policy_failed:"s + options.target});
        }
    }
    return {};
}

2.4 一切就绪

到此为止,System DE Master Key 以及 System DE Storage 已经就绪:

  • System DE Master Key 已经安装到 kernel keyring;
  • System DE Storage 文件夹的扩展属性已经设置了 Encryption Policy;

可以从下面的信息看出:

# xxd /data/unencrypted/ref
00000000: 599f 1aa4 3727 d401 198d d693 6a1f 2060  Y...7'......j. `

# ./fbe-get-policy_static /data/misc
[Info]: Detected support for FS_IOC_ADD_ENCRYPTION_KEY
[/data/misc]:
    .version = 2
    .flags   = 0xa
    .contents_encryption_mode  = 1   (AES_256_XTS)
    .filenames_encryption_mode = 4   (AES_256_CTS)
    .master_key_identifier[16] = 599F1AA43727D401198DD6936A1F2060
    >>>> SYSTEM DE Policy <<<<
# cat /proc/keys
0baa3b23 I------     1 perm 080b0000     0     0 keyring   fscrypt-dm-8: 6
0f65b28e I------   904 perm 08090000     0     0 ._fscrypt 599f1aa43727d401198dd6936a1f2060
2db96af7 I------     1 perm 080b0000     0     0 keyring   fscrypt-599f1aa43727d401198dd6936a1f2060-users: 1
37df89e6 I--Q---     1 perm 08010000     0     0 .fscrypt  599f1aa43727d401198dd6936a1f2060.uid.0
0903c23d I--Q---     1 perm 39010000     0     0 fscrypt-p 599f1aa43727d401198dd6936a1f2060: 108 [2]

接下来,在 System DE Storage 创建的任何文件或者文件夹,都会自动继承父文件夹的 Encryption Policy。

在 I/O 时,kernel 文件系统会根据 Encryption Policy 的 master_key_identifier,从 kernel keyring 中找到 Master Key,并随 BIO 传递到底层,对数据加密和解密。


3. User.0 CE Master Key

当用户设置用户锁屏密码后, User CE Master  key 的认证方式会改变,受用户密码保护,只有密码校验成功后,才能得到 User.0 CE Master Key,并将其安装到 kernel keyring。

User.0 CE Master Key 相关的认证数据被存储到目录 /data/misc/vold/user_keys/ce/0/current 下。我们对比一下未设置锁屏密码和设置锁屏密码后,该目录下的文件差异:

  • 未设置锁屏密码
> adb shell ls /data/misc/vold/user_keys/ce/0/current -l
-rw------- 1 root root   268 2022-11-05 22:08 encrypted_key
-rw------- 1 root root   194 2022-11-05 22:08 keymaster_key_blob
-rw------- 1 root root 16384 2022-11-05 22:08 secdiscardable
-rw------- 1 root root    10 2022-11-05 22:08 stretching
-rw------- 1 root root     1 2022-11-05 22:08 version
  • 设置锁屏密码
> adb shell ls /data/misc/vold/user_keys/ce/0/current -l
-rw------- 1 root root   268 2022-11-05 22:11 encrypted_key
-rw------- 1 root root 16384 2022-11-05 22:11 secdiscardable
-rw------- 1 root root     4 2022-11-05 22:11 stretching
-rw------- 1 root root     1 2022-11-05 22:11 version

可以看到设置锁屏密码后,少了文件 keymaster_key_blob。在介绍 System DE Master Key 时已经知道,keymaster_key_blob 是 KM TA 创建的一个用于加密 Master Key Blob 的 key(上文称其为 KSK),用于保证 Master Key Blob 的安全性,作为设备绑定认证的一种实现,即 KSK 只能在唯一的设备(和CPU、存储器等器件绑定)上可用。

在用户设置锁屏密码后,认证更加严格,不仅要唯一的设备,而且需要用户输入正确的锁屏密码。因此,Master Key Blob  不再用 KSK 加密,它已经不能满足需求。

疑问:encrypted_key 仍然存在,表明仍然使用对 Master Key Blob 加密的方式实现认证。但是加密 Master Key Blob 的 key 从哪里来?和用户锁屏密码存在什么关系?

我们先看以下用户设置锁屏密码时,导致 /data/misc/vold/user_keys/ce/0/current 下文件内容变化的流程:

android fbe,Android Security,android,linux,系统安全,安全,安全架构

AuthenticationToken 的实现:

// frameworks/base/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
static class AuthenticationToken {
	private final byte mVersion;
	/**
	 * Here is the relationship between these fields:
	 * Generate two random block P0 and P1. P1 is recorded in mEscrowSplit1 but P0 is not.
	 * mSyntheticPassword = hash(P0 || P1)
	 * E0 = P0 encrypted under syntheticPassword, recoreded in mEncryptedEscrowSplit0.
	 */
	private @NonNull byte[] mSyntheticPassword;
	private @Nullable byte[] mEncryptedEscrowSplit0;
	private @Nullable byte[] mEscrowSplit1;

	AuthenticationToken(byte version) {
		mVersion = version;
	}
	
	/**
	 * Derives a subkey from the synthetic password. For v3 and later synthetic passwords the
	 * subkeys are 256-bit; for v1 and v2 they are 512-bit.
	 */
	private byte[] deriveSubkey(byte[] personalization) {
		if (mVersion == SYNTHETIC_PASSWORD_VERSION_V3) {
			return (new SP800Derive(mSyntheticPassword))
				.withContext(personalization, PERSONALISATION_CONTEXT);
		} else {
			return SyntheticPasswordCrypto.personalisedHash(personalization,
					mSyntheticPassword);
		}
	}
	
	public byte[] deriveDiskEncryptionKey() {
		return deriveSubkey(PERSONALIZATION_FBE_KEY);
	}
	
	/**
	 * Re-creates synthetic password from both escrow splits. See javadoc for
	 * AuthenticationToken.mSyntheticPassword for details on what each block means.
	 */
	private void recreate(byte[] escrowSplit0, byte[] escrowSplit1) {
		mSyntheticPassword = bytesToHex(SyntheticPasswordCrypto.personalisedHash(
				PERSONALIZATION_SP_SPLIT, escrowSplit0, escrowSplit1));
	}
	
	/**
	 * Assign escrow data to this auth token. This is a prerequisite to call
	 * {@link AuthenticationToken#recreateFromEscrow}.
	 */
	public void setEscrowData(@Nullable byte[] encryptedEscrowSplit0,
			@Nullable byte[] escrowSplit1) {
		mEncryptedEscrowSplit0 = encryptedEscrowSplit0;
		mEscrowSplit1 = escrowSplit1;
	}
		
	/**
	 * Generates a new random synthetic password with escrow data.
	 */
	static AuthenticationToken create() {
		AuthenticationToken result = new AuthenticationToken(SYNTHETIC_PASSWORD_VERSION_V3);
		byte[] escrowSplit0 = secureRandom(SYNTHETIC_PASSWORD_LENGTH);
		byte[] escrowSplit1 = secureRandom(SYNTHETIC_PASSWORD_LENGTH);
		result.recreate(escrowSplit0, escrowSplit1);
		byte[] encrypteEscrowSplit0 = SyntheticPasswordCrypto.encrypt(result.mSyntheticPassword,
				PERSONALIZATION_E0, escrowSplit0);
		result.setEscrowData(encrypteEscrowSplit0,  escrowSplit1);
		return result;
	}
}

结合代码和流程图可以看到,用户设置密码后,框架会直接调到 vold 执行更换 MasterKey 认证方式:

  1. 调用函数 fscrypt_add_user_key_auth(user_id, serial, secret_hex),增加对 CE Master Key 的认证方式:
    1. 首先,加载 keymaster_key_blob ,使用 KSK 解密 encrypted_key,得到 Master CE Key Blob;
    2. 接着,生成新的 appid,appid 由 fscrypt_add_user_key_auth 传入的参数 secret_hex 组成;
    3. 将 appid Hash 成 AES Key;
    4. 使用生成的 AES Key 加密  Master CE Key Blob,得到 encrypted_key;
    5. 将新的认证数据存储到 /data/misc/vold/user_keys/ce/0/cx0000000000 下;
    6. 此时 /data/misc/vold/user_keys/ce/0 下已经存在两种 CE Master Key 的认证方式,如下所示:
      # 老的认证方式,通过 KM key KSK 认证
      > adb shell ls /data/misc/vold/user_keys/ce/0/current
      encrypted_key
      keymaster_key_blob
      secdiscardable
      stretching
      version
      
      # 新的认证方式,通过用户密码校验认证
      > adb shell ls /data/misc/vold/user_keys/ce/0/cx0000000000
      encrypted_key
      secdiscardable
      stretching
      version
  2. 调用 fscrypt_fixate_newest_user_key_auth,清除老的认证方式,改用用户密码认证,完成后,/data/misc/vold/user_keys/ce/0/cx0000000000 的内容将覆盖 /data/misc/vold/user_keys/ce/0/current。vold 会打出如下的 log :
    vold    : fscrypt_fixate_newest_user_key_auth 0
    vold    : Deleting key /data/misc/vold/user_keys/ce/0/current/keymaster_key_blob from Keymaster
    vold    : /system/bin/secdiscard
    vold    :     --
    vold    :     /data/misc/vold/user_keys/ce/0/current/encrypted_key
    vold    :     /data/misc/vold/user_keys/ce/0/current/secdiscardable
    vold    :     /data/misc/vold/user_keys/ce/0/current/keymaster_key_blob
    vold    : /system/bin/rm
    vold    :     -rf
    vold    :     /data/misc/vold/user_keys/ce/0/current
    vold    : Renaming /data/misc/vold/user_keys/ce/0/cx0000000000 to /data/misc/vold/user_keys/ce/0/current

从上面流程可以看到,函数 fscrypt_add_user_key_auth(user_id, serial, secret_hex)中的参数 secret_hex 是关键,可以认为 secret_hex 被用于加密 Master CE Key Blob。

  • secret_hex 是由 AuthenticationToken 中的 mSyntheticPassword 通过函数 deriveDiskEncryptionKey() 派生得到的 Key;
  • mSyntheticPassword 被用户用户密码间接加密,只有用户输入密码校验成功后,才能得到 mSyntheticPassword,进而得到  secret_hex;(mSyntheticPassword 加密的流程属于用户密码认证的一部分,不在这里介绍)

讨论:用户密码认证的方式并不是很完美,有很大的优化空间:

1.  secret_hex 的值每次都是固定的,这意味着某个环节存在漏洞导致 secret_hex 泄露,那么很轻易的得到 Master CE Key Blob,导致用户密码认证的方式彻底失效;

2. 即使用户更换密码或者关闭密码后重新设置密码,secret_hex 也不会改变,因为 SyntheticPassword 是固定的,导致风险大大增加;


FBE 密钥管理就介绍到这里,到此,System DE Master Key 、User DE/CE Master Key 已经就绪,System DE Storage、User DE/CE Storage 也已经就绪。在 I/O 时,底层文件系统、通用块层、UFS 驱动、ICE 驱动将配合完成数据流式加解密。在后文《【数据安全】5. Android 文件级加密(File-based Encryption)之高效 I/O》 详细介绍。


(*^_^*)有任何疑问,除了评论区还可以 对我发起悬赏提问 获取快速和高质量的回复 (*^_^*)文章来源地址https://www.toymoban.com/news/detail-785653.html

到了这里,关于【数据安全】4. Android 文件级加密(File-based Encryption)之密钥管理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 前端FileReader对象实现图片file文件转base64

    1、file转base64具体代码 2、原理解析 ​ 上面封装的方法,其原理主要是借助 FileReader 对象来实现图片格式的转换, FileReader 对象中的 readAsDataURL() 方法,可以读取一个 File 或 Blob 类型的文件,并将其转换为base64格式的字符串。但要注意的一点是:我们通过 readAsDataURL() 方法去读

    2023年04月09日
    浏览(40)
  • Base64加密解密,【微信小程序】,最新Android面试合集

    import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import tsou.com.encryption.R; import tsou.com.encryption.base64.Base64Utils; /** 一、什么Base64算法? Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一, Base64并不是安全

    2024年03月24日
    浏览(49)
  • Base64转二进制文件流以及转File、图片转Base64、二进制流转Base64

    1、Base64转二进制文件流 方法一: 调用示例: 方法二: 调用示例:  2、Base64转File 方法一: 调用示例: 方法二: 调用示例: 补充: 3、图片转Base64 调用示例: 4、二进制流转Base64 方法一: 调用示例: 方法二: 调用示例: 5、补充 5.1 atob() atob()  对经过 base-64 编码的字符

    2024年02月04日
    浏览(70)
  • Data Encryption Standard算法:历经考验的经典加密方案

    在当今数字化时代,数据安全是一个至关重要的问题。为了保护敏感数据的机密性和完整性,加密算法成为了数据保护的关键技术。其中,DES(Data Encryption Standard)算法作为一种经典的对称密钥加密算法,具有广泛的应用。本文将对DES算法的优点、缺点以及解决了哪些问题进

    2024年02月19日
    浏览(35)
  • js - 图片base64转file文件的两种方式

    最近项目中需要实现把图片的base64编码转成file文件的功能,然后再上传至服务器。 1.通过new File()将base64转换成file文件,此方式需考虑浏览器兼容问题 2.先将base64转换成blob,再将blob转换成file文件,此方法不存在浏览器不兼容问题 vue中配合vant的uploader上传组件使用案例: 打

    2024年02月14日
    浏览(51)
  • jmeter下载base64加密版pdf文件

    如下图所示,接口jmeter执行后, 返回一串包含大小写英文字母、数字、+、/、=的长字符串 ,直接另存为pdf文件后,文件有大小,但是打不开;另存为doc文件后,打开可以看到和接口响应一致的长字符串。 仔细查看该接口具体信息,感觉和 ContentType: application/octet-stream、Cont

    2024年02月22日
    浏览(51)
  • 【密码算法 之二】对称加密算法 AES(Advanced Encryption Standard)浅析

      AES的全称是 Advanced Encryption Standard,意思就是“高级加密标准”。它的出现主要是用于取代其前任DES算法的,因为我们都知道EDS算法的秘钥长度实际为56bit,因此算法的理论安全强度为2的56次方,但是随着计算能力的大幅提高,虽然出现了3DES的加密方法,但由于它的加密

    2024年02月05日
    浏览(53)
  • Ring Co-XOR encryption based reversible data hiding for 3D mesh model

    期刊:Signal Processing 作者:Lingfeng Qu et al. -- 加密域可逆数据隐藏被广泛应用于云存储数字媒体的内容安全、隐私保护和便捷管理。然而,RDH-ED技术在三维网格模型载体中的应用研究仍处于起步阶段。为解决现有针对三维网格模型的RDH-ED算法需要像第三方传输辅助信息,嵌入容

    2024年02月04日
    浏览(41)
  • 密码学学习笔记(九):Public-Key Encryption - 公钥加密2

    如果我们知道𝑝, 𝑞 (即𝑁 = 𝑝𝑞) 我们可以在mod N中进行反幂运算。 比如: 我们有一个单向陷门函数,非常适合加密。  取两个大素数,然后N = 𝑝𝑞, 然后挑选一对𝑒, 𝑑  加密:给定𝑃𝐾 = (𝑁, 𝑒) 和一条消息𝑚 在里面 计算密文𝑐 :  解密:给定一个密文𝑐

    2024年02月13日
    浏览(50)
  • 利用Base64加密算法将数据加密解密

    Base64准确来说并不像是一种加密算法,而更像是一种编码标准。 我们知道现在最为流行的编码标准就是ASCLL,它用八个二进制位(一个char的大小)表示了127个字符,任何二进制序列都可以用这127个字符表示出来。 而Base64则是用6个二进制位表示了64个字符,也就是说,任何的

    2024年04月09日
    浏览(95)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包