# 概述

## 简介
FDE（Full Disk Encryption，完全磁盘加密）是指在 Linux 操作系统上使用加密技术对整个硬盘或存储设备进行加密的过程。通过对整个硬盘进行加密，FDE 能够保护存储在磁盘上的所有数据，即使磁盘被物理盗窃或非法访问，未经授权的用户也无法读取数据。</br>

## FDE 原理
FDE 加密的核心思想是使用加密算法对硬盘的所有数据进行加密，通常在操作系统层级之前就对数据进行加密和解密。这意味着每次读写磁盘时，数据会被透明地加密和解密，而用户几乎不会感知到这一过程。

FDE 数据流如下

![fde_data_stream](_static/_images/fde/fde_data_stream.png)

FDE 的实现上依赖以下两个部分
- [cryptsetup: 管理磁盘加密工具](#cryptsetup)
- [dm-crypt: 基于 Device Mapper 的内核加密模块](#dm-crypt)

## 名词解释

| 缩写 | 全称/解释  |
|------|----- |
| FDE | Full Disk Encrypt，完全磁盘加密 |
| LUKS | Linux Unified Key Setup，Linux 统一密钥设置 |
| TEE | Trusted Execution Environment，可信执行环境 |
| AVB | Android Verified Boot，安卓验证启动 |
| Superblock | 超级块，用于存储文件系统的元数据和控制信息 |
| salt | 盐值，一段随机数据，对密码进行加盐处理能有效防止暴力破解 |


# FDE 工具

<span id="cryptsetup"/> </span>

## Cryptsetup

Cryptsetup 是一个用于管理磁盘加密的工具，专门用于加密磁盘分区和管理 LUKS 加密卷。它提供了多种加密算法和灵活的密钥管理功能，广泛应用于保护敏感数据。通过 Cryptsetup，用户可以安全地创建和管理加密磁盘，并防止数据被未经授权的访问，尤其是在物理设备被盗或丢失的情况下。

### 工作原理
当创建一个 LUKS 加密卷时，会对目标分区生成一个luks header用来存储 key 和 salt 等，并重新格式化除了 luks header 外的部分，生成新的加密后的 superblock。

![cryptsetup_intro](_static/_images/fde/cryptsetup_intro.png)


<span id="dm-crypt"/> </span>

## dm-crypt

dm-crypt 是 Linux 系统中一个基于 Device Mapper 技术的加密模块，运行在内核空间。用于对存储设备（如硬盘、分区、LVM 卷等）进行加密。它提供了一种透明的加密机制，使得操作系统可以在不暴露加密细节的情况下，安全地处理数据。简而言之，dm-crypt 允许用户加密存储设备上的数据，确保数据的机密性，即使设备被盗或丢失，数据也无法被轻易访问。

### 工作原理
dm-crypt 是基于 Device Mapper（设备映射器）技术实现的，它通过创建一个新的虚拟设备，将加密层与存储层分离。操作系统通过虚拟设备进行所有的读写操作，而加密和解密的过程在后台自动完成。

当你对加密设备进行读写时，dm-crypt 会自动将数据加密（写入）或解密（读取）。这种加密/解密过程是透明的，应用程序或用户无需了解加密细节。

![dm-crypt_read_write](_static/_images/fde/dm-crypt_read_write.png)

# X5 FDE用户密码管理
在全盘加密方案中，用户需要为加密的磁盘设置一个密码或密钥，只有提供正确的密码才能访问数据。这些密码和密钥管理的安全性直接决定了整个加密系统的强度。
X5 关于 FDE 密钥管理，提供了如下两种方案

1. Cryptsetup 官方方案，使用 user password 为外部输入，例如保存在文件系统中的 user password 文件。
2. 本文介绍的方案 user passwd 由 TEE 派生出来，不保存在存储介质。

两者主要区别是 user password 来源不同

**注意: X5 推荐使用方案2**

- **方案一: Cryptsetup 官方方案**

```
cryptsetup -c aes-xts-plain64 -s 256 -h sha256 -q luksFormat /dev/mmcblk0p1 -d /etc/fde_default.bin
cryptsetup -c aes-xts-plain64 -s 256 luksOpen /dev/mmcblk0p1 luksdata -d /etc/fde_default.bin
```

- **方案二: 通过 TEE 派生 user passwd**

X5 通过修改 Cryptsetup 源码，其方案特性如下
- user passwd 由TEE派生出来
- user passwd 不保存在存储介质，只是在 Cryptsetup 初始化的时候使用
```
getdmkey --km | cryptsetup -c aes-xts-plain64 -s 256 -h sha256 -q luksFormat /dev/mmcblk0p1
getdmkey --km | cryptsetup -c aes-xts-plain64 -s 256 -h sha256 luksOpen /dev/mmcblk0p1 luksdata
```

cryptsetup 命令参数说明
- `-c, --cipher`: 指定加密算法，例如 `aes-xts-plain64` 是 dm-crypt 中常用的一种加密算法配置，它指定了以下内容
    - `aes`: 使用 AES 对称加密算法，
    - `xts`: 使用 XTS 加密模式，适用于磁盘加密等场景
    - `plain`: 表示没有额外的额外层次加密或包装
- `-s, --key-size`: 指定密钥长度（单位: 比特），如 128 位或 256 位。通常建议使用 256 位的密钥大小
- `--hash`: 指定哈希算法，如 sha256 或 sha512，用于生成加密密钥的哈希值。
- `-d, --key-file`: 指定密钥文件的路径
- `-q, --batch-mode`: 批处理模式下运行 cryptsetup 命令，操作将不会进行任何交互式提示，所有的确认和用户输入都被自动跳过
- `luksFormat`: 用于格式化一个磁盘分区或设备为 LUKS 加密格式
- `luksOpen`: 解锁 LUKS 加密卷，将其映射为一个设备
- `luksdata`: 指定 device mapper 映射的节点名，用户可以指定为任意名称，此处使用 `luksdata` 仅作为演示

# X5 FDE 使用方法

当前 X5 支持 system 分区和用户分区加密
- system 分区加密可参考如下方案
  - [system 分区加密](#fde_solution_1)

- 用户分区加密支持 key-hobot 和 key-file 方案
  - [key-hobot 方案](#fde_solution_2)
  - [key-file 方案](#fde_solution_3)


<span id="fde_solution_1"/> </span>

## system 分区加密

### 方案原理
该方案实现参考了 system 的 dm-verity 实现流程:

- 编译阶段: 在编译 system FDE 镜像时，FDE 相关的配置信息保存到了 vbmeta 分区
- uboot 阶段: uboot 从 vbmeta 分区获取 system FDE 信息，并把此信息通过 bootargs 传递到 kernel
- kernel 阶段: 由 kernel 的 dm-crypt 框架解析 bootargs，完成 system 分区的解密

在 bootargs 中的 FDE 信息示例如下:

```
dm-mod.create="dm-crypt,,0,ro,0 510976 crypt aes-cbc-essiv:sha256 bf05fb0da6bc94f6b550e23a093d5fd2 0 $(SYSTEM_PART) 0 1 allow_discards
```

### 使用方法

#### 把 system 分区的验证方式改为 crypt
根据项目修改配置文件，以 `device/horizon/x5/board_x5_evb_debug_config.mk` 为例
```shell
# system 配置
# 指定根文件系统类型和预编译的文件系统路径
export HR_SYSTEM_TYPE="buildroot"
export HR_SYSTEM_DIR=${HR_TOP_DIR}/system/buildroot/prebuilt
# 根文件系统的分区名，需要和分区表配置对应
export HR_SYSTEM_PART_NAME="system"
# system verify method: dm-verity, crypt
export HR_SYSTEM_VERIFY="crypt"
```

**注意: 修改配置后，需重新执行 `./bd.sh lunch` 选择该配置文件，使选项生效**

#### 制作 system FDE 镜像

当前FDE key由编译时随机产生，不落盘, 长度为128 bit。

**注意: 如何防止 system FDE 信息泄露**
在开发板上通过 bootargs 传递 system FDE 信息，因此可以通过启动 log 查看 cmdline 获取 system FDE的信息。
因此为了防止 FDE key 的泄露，在 bootargs 中的 key 是由 efuse 中 user root key 加密后的 FDE key （其加密方式为 `AES-128-ECB，nopad`）
在 kernel 的 dm-crypt 框架中添加了对 bootargs 的解密 key 流程，解密流程在 TEE 侧完成。

要触发 key 的解密，需要 bootargs 中的 key 添加前缀 `dr-fde:`，如下所示:
```
dm-mod.create="dm-crypt,,0,ro,0 510976 crypt aes-cbc-essiv:sha256 dr-fde:bf05fb0da6bc94f6b550e23a093d5fd2 0 $(SYSTEM_PART) 0 1 allow_discards
```

#### user root key

user root key位于 `device/horizon/x5/board_cfg/soc/bl2_cfg/user_root.key` ，长度16字节

- 生成用户 key
用户可以更换user root key，例如：随机生成新的 key，操作如下
```shell
cd device/horizon/x5/board_cfg/soc/bl2_cfg/
dd if=/dev/random of=./user_root.key bs=1 count=16
```

- 烧写 key
  - user root key的烧写请参考 [烧写efuse](../efuse/efuse_update.html#span-id-bl2-update-efuse-bl2efuse)

**注意: 没烧写 user root key，默认为16个字节的0x0**

#### 编译与启动
执行 `./bd.sh` 重新编译 disk 镜像并且烧录到主板启动。

其中，system FDE镜像的制作脚本是 `build/tools/partition_tools/pack_avb_img.sh`

**注意:**
- system FDE镜像的制作需要sudo权限
- 必须为 secure boot 启动，且开启 AVB 验证，否则不会触发 system 分区的解密而导致根文件系统挂载失败
- system的加密信息保存在vbmeta分区，因此必须OTA升级或烧录时必须同时升级vbmeta/boot/system分区，并且这三个分区镜像是在同一次编译出产生的



<span id="fde_solution_2"/> </span>

## 用户分区加密: key-hobot 方案

实现普通分区的 FDE 方案，以分区 data 为例，操作步骤如下

#### 新增分区
在分区表中新增 data 分区，以 `device/horizon/x5/board_cfg/soc/x5-soc-debug-gpt.json` 为例（用户以实际分区表文件为准）
```diff
diff --git a/board_cfg/soc/x5-soc-debug-gpt.json b/board_cfg/soc/x5-soc-debug-gpt.json
index 10500f3..22a3421 100644
--- a/board_cfg/soc/x5-soc-debug-gpt.json
+++ b/board_cfg/soc/x5-soc-debug-gpt.json
@@ -39,6 +39,10 @@
                "part_type": "GOLDEN",
                "size": "700m"
        },
+       "data": {
+               "size": "50m"
+       },
        "userdata": {
                "fs_type": "ext4",
                "size": "50m"
```

**注意：**

- 若新增包含内容的分区，则该分区在分区表中的前一个分区必须为非空（即实际会生成 .img 镜像文件）。如果是新增空分区，则不受此限制。
- 新增的分区名称中不得包含下划线 `_` ，否则分区名称将在编译脚本中被截断，从而导致异常。
- 如果把原有分区改成 FDE，需要在 `system/buildroot/prebuilt/boot-utils-runtime/etc/hb-fstab` 中删除对应分区的 mount 规则，否则会 mount 失败。

#### 在 hb-crypttab 中添加 FDE 相关配置
路径 `system/buildroot/prebuilt/boot-utils-runtime/etc/hb-crypttab` 添加 data 分区的 FDE 配置，如下
```shell
# <target name>	<source device>		<key file>	<options>
data /dev/block/platform/by-name/data --key-hobot cipher=aes-xts-plain64,size=512,ext4=y
```

配置格式为 `<target name> <source device> <key file> <options>`, 参数说明
- `target name`: 加密分区名，也是在/dev/mapper/下的名字
- `source device`: 设备路径，当前会对分区设备自动创建 by-name 链接，by-name 路径都在 `/dev/block/platform/by-name` 下，例如对应 eMMC 分区设备，会链接到 `/dev/mmcblk\*p\*`
- `key file`：`--key-hobot` 表示使用TEE派生出来的 user passwd；另外也可以指定 key 路径, 例如 `/etc/fde_default.bin`
- `options`：其他参数设置。
  - `cipher=aes-xts-plain64`: 表示指定加密算法，注意需要和 cryptsetup 的 `-c` 参数匹配
  - `size=512`: 表示key size，注意需要和 cryptsetup 的 `-s` 参数匹配
  - `hash=sha512`: 表示对key做HASH，对应 cryptsetup 的 `-h` 参数
  - `ext4=y`: 表示加密分区的文件系统格式，加密初始化之后需要对分区格式化之后才能挂载

**注意: 如果要替换默认的 FDE key，编译时需要替换 `system/buildroot/prebuilt/boot-utils-runtime/etc/fde_default.bin`**

#### 在根文件系统中创建挂载节点目录

修改 system 编译脚本 `build/mk_system.sh`，增加 data 分区的挂载点目录

```shell
function build_unpack()
{
	...(省略)...

    # 创建分区挂载目录
    mkdir -p ${SYSTEM_BUILD_DIR}/{app,log,userdata,usr/hobot,data,private}

    ...(省略)...
}
```
重新编译

#### 编译与启动
执行 `./bd.sh` 重新编译 disk 镜像并且烧录到主板启动。

如下图，开启成功后在 `/dev/mapper` 下将会出现 data：

![normal_fde_res](_static/_images/fde/normal_fde_res.png)

查看启动 log
```
[   27.455427] CRYPTSETUP: cipher value: aes-xts-plain64
[   27.460926] CRYPTSETUP: /dev/block/platform/by-name/data
[   27.485172] INFO: fde luksFormat /dev/block/platform/by-name/data starting
[   27.505462] CRYPTSETUP: key:--key-hobot src:/dev/block/platform/by-name/data dst:data
[   40.135874] CRYPTSETUP: data
[   40.142366] mount -t ext4 /dev/mapper/data /data
[   40.235217] ext2fs_open2:
[   40.235270] Bad magic number in super-block

[   40.243783] fsck.ext4: Superblock invalid, trying backup blocks...
[   40.267239]
               The
[   40.267259] super
[   40.271303] block
[   40.273973] udevd[1265]: conflicting device node '/dev/mapper/data' found, link to '/dev/dm-0' will not be created
[   40.278746]  could not be read or does not describe a valid ext2/ext3/ext4
[   40.298703] filesystem
[   40.298709] .  If the
[   40.302940] printk: fsck.ext4: 14 output lines suppressed due to ratelimiting
[   40.332572] Creating filesystem with 34816 1k blocks and 8720 inodes
[   40.338968] Filesystem UUID: e57ae31f-6f33-4c02-a2c0-6d714aeebb57
[   40.345097] Superblock backups stored on blocks:
[   40.345105]

[   40.349845] 8193
[   40.352905] ,
[   40.354750] 24577


[   40.361360] Allocating group tables:
[   41.841061] printk: mkfs.ext4: 22 output lines suppressed due to ratelimiting
[   41.885513] Pass 1: Checking
[   41.885533] inode
[   41.888517] s,
[   41.890479] block
[   41.892236] s, and sizes
[   41.904447] Pass 2: Checking
[   41.904464] directory
[   41.907447]  structure
[   41.914925] Pass 3: Checking
[   41.914939] directory
[   41.949896] printk: fsck.ext4: 6 output lines suppressed due to ratelimiting
[   41.966145] EXT4-fs (dm-0): mounted filesystem with ordered data mode. Quota mode: disabled.
```

<span id="fde_solution_3"/> </span>

## 用户分区加密: key-file 方案

### 方案原理

该方案实现了在编译阶段使用 key 文件加密用户指定分区，并在启动阶段使用相同的 key 文件对用户分区进行解密并挂载
下面以新增用户分区 `fdedata` 为例，介绍其使用方法

- 编译阶段: 使用指定 key 文件对用户分区镜像进行加密
- 启动阶段: 启动脚本根据配置文件，对用户分区解密，并挂载到指定目录

### 使用方法

#### 1. 新增分区与编译脚本
在分区表中新增 `fdedata` 分区，以 `device/horizon/x5/board_cfg/soc/x5-soc-debug-gpt.json` 为例（用户以实际分区表文件为准）
```json
"fdedata": {
  "fs_type": "ext4",
  "part_type": "GOLDEN",
  "size": "32m",
  "fde_type": "key-file"
},
```
参数说明
- `fs_type` : 文件系统格式，目前仅支持对 ext4 格式分区加密
- `size` : 分区大小
- `fde_type` : 用于在编译阶段指定分区加密类型，目前仅支持指定 `key-file` 方式


**注意: 如果挂载点不存在，启动脚本将自动创建，需注意如果挂载点在 `/`，由于 rootfs 默认为只读，需要在编译阶段提前创建目录，避免目录创建失败**

#### 2. 加解密 key 文件
默认加解密使用的 key 文件路径 `system/buildroot/prebuilt/boot-utils-runtime/etc/fde_default.bin`，用户可替换为实际使用 key 文件。

示例: 通过随机数生成 key 文件

**注意: key 文件大小需要与加密算法密钥长度匹配，目前用户分区加密 key 长度支持 256/512**

以 AES-128 对应 key 长度 16字节为例

```shell
dd if=/dev/urandom  of=system/buildroot/prebuilt/boot-utils-runtime/etc/fde_default.bin  bs=1 count=16
```

新增 `fdedata` 分区的解密挂载配置，路径 `system/buildroot/prebuilt/boot-utils-runtime/etc/hb-crypttab`。
```shell
# <target name>	<source device>		<key file>	<options>
fdedata  /dev/block/platform/by-name/fdedata /etc/fde_default.bin dm_crypt,cipher=aes-xts-plain,size=256,mntext4=/userdata/test
```
配置文件的格式为: `[分区名] [分区路径] [key文件路径] [参数]`

参数支持多个选项，每个选项以逗号隔开，选项说明如下
- `dm_crypt` : 表示使用 dm-crypt 加密用户分区
- `cipher` : 加密算法模式，注意此处应与编译脚本一致，X5 SDK 默认使用 `aes-xts-plain`
- `size` : 密钥长度，支持 256/512
- `mntext4` : 使用 ext4 格式挂载点路径

#### 3. 新增加密分区编译脚本
为了让用户更好的控制编译逻辑，X5 SDK 对每个分区都使用了独立的编译脚本，针对用户加密分区编译，X5 SDK 提供了脚本模板 `build/mk_fde.sh`，用户需复制并修改为用户分区名，格式为 `mk_[分区名].sh`，以新增分区 `fdedata` 为例，需复制并重命名脚本为 `mk_fdedata.sh`。

**注意:脚本文件名必须严格按照以上格式重命名，否则将导致编译报错**

用户可以在编译脚本中添加自定义操作，如下
```shell
#...(省略代码)...
function build_all()
{
	echo -e "\033[33m[INFO]: Starting Build User FDE Part [${PART_NAME}].\033[0m"
	echo "Part Name: ${PART_NAME}"
	echo "SRC Folder: ${user_part_dir}"
	echo "Fs Type: ${user_fs_type}"
	echo "FDE Type: ${fde_type}"

  # The user adds custom actions here. Begin

  # 用户在此添加自定义操作

  # End

	# Build EXT4 image
	if [ "${user_fs_type}" = "ext4" ]; then
		[ ! -d $user_part_dir ] && mkdir -p ${user_part_dir}
		${HR_PARTITION_TOOL_PATH}/mk_avb_fs.sh ${PART_NAME} ${user_part_dir}
	fi
#...(省略代码)...
```

在 `xbuild.sh` 增加 `fdedata` 的编译入口

```diff
+++ b/xbuild.sh
@@ -126,6 +126,11 @@ function build_app
        build_component "app" "${HR_LOCAL_DIR}/mk_app.sh" "$@"
 }

+function build_fdedata
+{
+       build_component "fdedata" "${HR_LOCAL_DIR}/mk_fdedata.sh" "$@"
+}
+
 function truncate_fill_image
 {
        part_size=$(get_part_attr "${1}" "size")
@@ -408,7 +413,7 @@ function build_all
        build_pack "$opt"
 }

-avail_func=("all" "lunch" "miniboot" "uboot" "factory" "boot" "hbre" "system" "app" "pack" "otapackage")
+avail_func=("all" "lunch" "miniboot" "uboot" "factory" "boot" "hbre" "system" "app" "pack" "otapackage" "fdedata")

 if [ $# -eq 0 ];then
        build_all all
```

#### 4. 用户加密分区源文件
用户分区源目录位于 SDK 根目录的 `custom/[分区名]`

**注意: X5 SDK 默认并不包含此目录，需用户自行创建**

以新增分区 `fdedata` 为例，用户需新增目录 `custom/fdedata`，并存放用户数据文件

```
custom/
└── fdedata
    ├── a.txt
    ├── b.txt
    ├── c.txt
    ├── d.txt
    └── f.txt
```

#### 5. 编译
**全编译**

在 SDK 根目录执行 `sudo ./bd.sh`，将自动编译并生成分区加密镜像，其原理步骤如下
- 将 `custom/fdedata` 目录打包为 ext4 格式镜像
- 使用 key 文件对 ext4 镜像镜像加密，生成 `out/product/fdedata.img`
- 将 `out/product/fdedata.img` 打包进固件镜像 `emmc_disk.simg`

**分区编译**

用户也可以对加密分区镜像进行单独编译，编译命令格式为 `sudo ./bd.sh [分区名]`

**注意: 编译加密分区需要获得 root 权限**


### 启动验证
系统启动脚本 `/etc/init.d/S95forcefde.sh`，将读取配置 `/etc/hb-crypttab`，使用其参数选项解密分区，以分区 `fdedata` 为例:
其配置如下:
```shell
# <target name> <source device>         <key file>      <options>
fdedata  /dev/block/platform/by-name/fdedata /etc/fde_default.bin dm_crypt,cipher=aes-xts-plain,size=256,mntext4=/userdata/test
```

启动log如下:
```
[    4.799535] udevd[302]: conflicting device node '/dev/mapper/fdedata' found, link to '/dev/dm-1' will not be created
[    4.857102] INSECURE MODE FOR /etc/fde_default.bin
[    4.863015] CRYPTSETUP: cipher value: aes-xts-plain
[    4.863393] Mount Point: /userdata/test
[    4.866874] CRYPTSETUP: /dev/block/platform/by-name/fdedata
[    4.914790] CRYPTSETUP: dm_crypt params  --type plain -c aes-xts-plain -s 256
[    4.914908] CRYPTSETUP: dm_crypt key -d /etc/fde_default.bin
[    4.976727] CRYPTSETUP: fdedata
```
系统将分区 `fdedata` 解密并生成节点 `/dev/mapper/fdedata`。最后将解密分区自动挂载到目录 `/userdata/test`

```
root@buildroot:~# mount | grep fdedata
/dev/mapper/fdedata on /userdata/test type ext4 (rw,relatime)
```