磁盘加密
适用于:Jetson Xavier NX 系列、Jetson AGX Xavier 系列和 Jetson TX2 系列
磁盘加密对整个磁盘或分区进行加密,以保护其中包含的数据。Jetson Linux 提供的磁盘加密基于
Linux 统一密钥设置 (LUKS)
静态数据加密,这是 Linux 磁盘加密的标准。它提供了一种标准磁盘格式,该格式将所有必要的设置信息存储在磁盘本身的分区标头中。Trusty 架构中的密码短语支持具有多个用户密码的磁盘加密功能。
设置准备
Jetson Linux 使用 cryptsetup(一种 LUKS 用户空间命令行实用程序)来设置和解锁加密磁盘。它使用 DMCrypt 内核模块作为其后端。该实用程序将加密磁盘设置为 LUKS 分区,并使用密码短语对其进行配置。
DMCrypt 内核模块是 Linux 内核中加密功能的标准设备映射器接口。它位于磁盘驱动程序和文件系统之间,在它们之间透明地加密和解密数据块。
有关
cryptsetup 和
DMCrypt 的更多信息,请参阅
cryptsetup 项目和
DMCrypt 文档,两者均由 GitLab 托管。
操作细节
磁盘加密使用两种类型的密钥
• 主密钥,也称为 磁盘加密密钥 (DEK)。此密钥用于在文件系统和磁盘之间传输数据时进行数据加密或解密。
主密钥由 cryptsetup 生成。用于生成密钥的数据的默认和推荐来源是 /dev/random。密钥驻留在 LUKS 标头中,并由从密码短语派生的密钥加密。
• 密码短语或密码是用户提供的输入字符串或模式,用于设置磁盘加密和锁定磁盘。相同的输入用于解密和解锁存储在磁盘上的数据。
密码短语在两个应用程序的帮助下生成
• luks-srv:一个可信应用程序 (TA),它查询 EKB 密钥并使用它来派生 LUKS 密钥,LUKS 密钥是派生密码短语的根密钥。
• nvluks-srv-app: 一个普通(非安全)世界中的客户端应用程序 (CA),它与 luks-srv 通信以检索密码短语。cryptsetup 使用密码短语来解锁加密磁盘。
用于磁盘加密的加密算法是 AES-CBC with ESSIV,使用 128 位密钥长度。这种操作模式使加密数据看起来完全随机,从而最大限度地减少了攻击的可能性。整个密钥派生过程在安全世界中执行。下图概述了该过程
生成过程使用此密钥派生函数 (KDF) 来生成每设备唯一 LUKS 密钥或通用密钥
其中
• in-key 是从中派生密钥的字符串。在 L4T 实现中,它是 EKB 密钥。
• label-string 标识要生成的密钥的用途。在参考实现中,label-string 是由 TA 定义的固定值。
• context-string 是一个包含与派生密钥相关的信息的字符串,KDF 将其用作密钥生成的参数。在参考实现中,默认上下文字符串是磁盘的 UUID,但您可以通过修改 TA 来更改此设置。
要生成每设备唯一 LUKS 密钥,label-string 为 "luks-srv-ecid",context‑string 为 "$(ECID)"(对环境变量 ECID 的引用)。
要生成通用密钥,label-string 为 "luks-srv-generic",context‑string 为 "generic-key"。
创建 LUKS 密钥后,来自普通世界的 nvluks-srv-app CA 可以查询 luks-srv TA 以获取密码短语。
nvluks-srv-app CA 通过向 TA 发送 IPC 数据包来与 TA 通信。这是数据包的结构
typedef struct luks_srv_cmd_msg {
uint32_t luks_srv_cmd;
char context_str[40];
char output_passphrase[16];
} luks_src_cmd_msg_t;
数据包的数据成员为
• luks_srv_cmd 是命令消息,用于告知 TA 如何处理数据包。LUKS_GET_UNIQUE_PASS 告知 TA 生成每设备唯一密码短语;LUKS_GET_GENERIC_PASS 告知 TA 生成通用密码短语。
• context_str 是传递给 KDF 以生成密码短语的上下文字符串。在此实现中,输入上下文字符串是磁盘 UUID,因此创建加密磁盘映像或在启动时解锁它无需用户交互。
• output_passphrase 是 TA 返回的密码短语。通过来自 nvluks-srv-app 的输出密码短语,它可以用于解锁加密磁盘。
此示例演示了如何使用 nvluks-srv-app 解锁加密磁盘。
#!/bin/bash
# 使用 "cryptsetup" 和 "nvluks-srv-app" 解锁加密设备
nvluks-srv-app --context-string "${UUID}" \
--get-unique-pass | \
cryptsetup -c aes-cbc-essiv:sha256 \
-s 128 \
luksOpen \
<luksDevice> <DM_name>
注意 | 为了防止在启动时(例如在 initrd 中)提取密码短语的任何形式的攻击,请使用 luks_srv_cmd 命令类型 LUKS_NO_PASS_RESPONSE。此命令指示 luks-srv 在重新启动之前不响应 LUKS_GET 命令(以获取密码短语)。此步骤对于确保您设计的安全性至关重要。 |
威胁模型
磁盘加密的目的是防止攻击者窃取或篡改磁盘上的数据。即使物理卸载磁盘(或者,对于 eMMC 等内部设备,从设备中移除),数据也不会泄露。
磁盘加密无法防范以下类型的威胁
• 具有安全漏洞的后台进程或守护程序。攻击者可能能够利用该漏洞来控制进程并访问磁盘。
• 登录 ID 和密码的盗窃或泄露。攻击者可以使用这些凭据登录设备并访问磁盘。
Jetson Linux 中的磁盘加密实现
Jetson Linux 提供了磁盘加密的参考实现,该实现满足许多用例的安全要求。如果您的用例的要求不同,您可以对其进行修改或将其用作实现您自己的模型的参考。
加密磁盘的布局
由于引导加载程序无法读取加密文件,因此磁盘加密要求 L4T 将“原始”系统的 APP 分区分为两个
• 未加密的 APP 分区包含文件系统的 /boot 分支,包括内核、DTB 和 initrd 映像
• 新的加密 APP_ENC 分区包含文件系统的其余部分。
以下是未启用磁盘加密的系统上的 APP 分区定义的示例。您可以在 L4T BSP 分区配置文件中找到类似这样的定义,例如,Linux_for_Tegra/bootloader/t186ref/cfg/flash_t194_sdmmc.xml 用于从 SDMMC 内存启动的 Jetson AGX Xavier™ 系列设备。
<partition name="APP" type="data">
<allocation_policy> sequential </allocation_policy>
<filesystem_type> basic </filesystem_type>
<size> APPSIZE </size>
<file_system_attribute> 0 </file_system_attribute>
<allocation_attribute> 0x8 </allocation_attribute>
<align_boundary> 4096 </align_boundary>
<percent_reserved> 0 </percent_reserved>
<filename> APPFILE </filename>
<unique_guid> APPUUID </unique_guid>
<description>
**必需。** 包含 rootfs。必须定义此分区
在 `primary_GPT` 之后,以便可以将其作为固定的已知
特殊设备 `/dev/mmcblk0p1` 访问。
</description>
</partition>
以下示例显示了磁盘加密如何将 APP 分隔为两个分区。您可以在 L4T BSP 中找到该文件,例如,Linux_for_Tegra/bootloader/t186ref/cfg/flash_t194_sdmmc_enc_rfs.xml 用于与上述设备相同的设备。
<partition name="APP" type="data">
<allocation_policy> sequential </allocation_policy>
<filesystem_type> basic </filesystem_type>
<size> 104857600 </size>
<file_system_attribute> 0 </file_system_attribute>
<allocation_attribute> 0x8 </allocation_attribute>
<align_boundary> 4096 </align_boundary>
<percent_reserved> 0 </percent_reserved>
<filename> system_boot.img </filename>
<unique_guid> APPUUID </unique_guid>
<description>
**必需。** 包含 boot 分区。必须定义此分区
在 `primary_GPT` 之后,以便可以将其作为固定的
已知特殊设备 `/dev/mmcblk0p1` 访问。
</description>
</partition>
<partition name="APP_ENC" type="data" encrypted=”true”>
<allocation_policy> sequential </allocation_policy>
<filesystem_type> basic </filesystem_type>
<size> APP_ENC_SIZE </size>
<file_system_attribute> 0 </file_system_attribute>
<allocation_attribute> 0x8 </allocation_attribute>
<align_boundary> 4096 </align_boundary>
<percent_reserved> 0 </percent_reserved>
<filename> system_root_encrypted.img </filename>
<unique_guid> APP_ENC_UUID </unique_guid>
<description>
**必需。** 包含加密的 root 分区。
</description>
</partition>
第一个示例的 APP 定义与第二个示例的 APP 和 APP_ENC 定义之间的差异以红色字体突出显示。APP_ENC 定义中的元素与 APP 中的元素相同。
请注意,APP 的 <size> 元素指定了一个实际数字,但 APP_ENC 的 <size> 元素指定了一个符号 APP_ENC_SIZE。稍后,必须通过从总 rootfs 大小中减去 APP 的大小来计算 APP_ENC_SIZE 的值。
每个分区的 <filename> 元素指定相应磁盘映像的实际文件名(而不是在刷写期间解析为文件名的符号)。
分区的 <unique_guid> 元素分别指定符号 APPUUID 和 APP_ENC_UUID。这两个符号都由映像生成过程转换为真实的 UUID 数字。
APP_ENC 分区的 encrypted 属性指示该分区已加密。
使用新的分区布局,板级配置文件中需要新的参数来启用磁盘加密并应用新的分区布局文件。为您的设备使用合适的板级配置文件,例如,Linux_for_Tegra/p2972-0000.conf.common 用于 Jetson AGX Xavier 开发者套件中的 Jetson AGX Xavier 模块。disk_enc_enable 设置指示已启用磁盘加密,EMMC_CFG 标识要使用的分区布局文件
disk_enc_enable=1;
EMMC_CFG=flash_l4t_t194_sdmmc_enc_rfs.xml;
刷写工具使用板级配置文件生成文件系统映像,并将它们刷写到设备上。
如何创建文件系统映像
当您创建文件系统映像以支持使用加密磁盘启动时,请记住以下几点
• bootarg 内核命令行的 root 参数必须使用 UUID 来标识要从加密 root 磁盘启动的根磁盘。
• initrd 中的 crypttab 文件包括加密磁盘的挂载点和设备名称。它可以是像 /dev/mmcblk0p1 这样的真实设备名称,也可以是磁盘的 UUID。
• rootfs 中的 fstab 文件已使用 APP 分区中的 /boot 目录进行更新。这有助于 automount 进程在启动时挂载分区。
如何在主机上创建加密 Rootfs
rootfs 在主机上由 flash.sh 生成。下图显示了 rootfs 的元素(绿色)、用于生成它的实用程序(蓝色)和输入(红色)。
输入包含两个部分:用于磁盘加密的 EKB 密钥的纯密钥文件,以及用于生成密码短语的输入字符串。默认情况下,输入字符串是加密磁盘的 UUID。您可以修改生成 rootfs 的脚本,以允许用户输入他们自己的字符串。您必须相应地更改 initrd,使其使用用户提供的字符串。
您必须在安全系统上生成 rootfs,即配备硬件安全模块 (HSM) 的安全主机计算机。HSM 用于密钥生成和管理,以保护密钥资产并安全地传输到工厂车间。这对于确保密钥不会泄露到生产线上的不安全系统至关重要。flash.sh 调用两个辅助脚本来生成密码短语和磁盘映像
• gen_luks_passphrase.py 遵循与 hwkey-agent 和 luks-srv TA 相同的过程来派生 LUKS 密钥,并使用该密钥生成密码短语。
• disk_encryption.sh 输出 initrd、system_boot.img 和 system_root_encrypted.img 的磁盘映像。
Jetson Linux 参考实现仅生成每设备加密磁盘映像。将其用作起点,开发适合您的用例和生产环境的脚本。
以下是使用 flash.sh 的命令示例
# EKB 分区中的磁盘加密密钥
$ echo "96cdb5da247b37bb536e9f5506d37e52" > ekb.key
$ sudo ROOTFS_ENC=1 ./flash.sh -i “./ekb.key” <board> <rootdev>
flash.sh 命令行开关 ‑‑i 指定要用于磁盘加密的密钥。上面描述的参考实现使用 EKB 密钥。您可以自定义脚本以使用不同的密钥源。如果您这样做,请相应地审查整个密钥派生和密码短语生成流程,以使一切正常工作。
如何将加密 Rootfs 刷写到外部存储设备
要将
flash.sh 生成的加密 rootfs 刷写到外部存储设备,请使用
l4t_initrd_flash.sh(也称为“initrd 刷写工具”)。(请参阅主题
刷写和启动目标设备 中的
使用 initrd 刷写 部分)。
以下是使用 l4t_initrd_flash.sh 将加密 rootfs 刷写到连接到 Jetson AGX Xavier 系列设备的 NVMe SSD 的命令示例
$ cd Linux_for_Tegra
$ sudo ROOTFS_ENC=1 ./tools/kernel_flash/l4t_initrd_flash.sh --external-device nvme0n1p1 \
> -c ./tools/kernel_flash/flash_l4t_nvme_rootfs_enc.xml --external-only -S 8GiB \
> jetson-xavier external
用于使用 initrd 刷写加密 rootfs 的工具和说明可以在目录 /Linux_for_Tegra/tools/kernel_flash/ 中找到。有关更多详细信息,请参阅该目录中的 README_initrd_flash.txt。
增强 initrd 以解锁加密 Rootfs
要从加密的 root 文件系统启动,您需要一个 initrd 映像,该映像包括必要的实用程序(例如 cryptsetup)和脚本,以便在内核初始化后但在操作系统的其余部分启动之前设置根设备。该映像必须包含 APP 分区(其中包含 rootfs 的 /boot 分支(未加密))和 APP_ENC 分区(其中包含 rootfs 的其余部分(已加密))。
下面的启动流程图显示,默认情况下,引导加载程序 (CBoot) 使用 APP 分区作为启动分区。U‑Boot 检查主 GPT 之后的第一个分区,该分区也是 APP 分区。因此,加密磁盘的分区布局适用于任一引导加载程序。)
initrd 中的 init 脚本执行以下步骤来检查加密的根设备、解锁它并挂载它
1. 验证 root 参数是否指定与 /etc/crypttab 中的根设备设置相同的设备。
2. 验证根设备是否为 LUKS 设备。
3. 使用每设备唯一密码短语解锁加密的根设备。
4. 挂载根设备。
命令 luks-srv TA 在重新启动之前不响应进一步的密码短语请求。
修改 initrd 以解锁其他加密文件系统
initrd 中保存的文件 /etc/crypttab 描述了在系统启动期间设置的加密块设备。此文件的每一行都具有以下形式
<卷名> UUID=<uuid>
其中
• <卷名> 是要放置解密数据的卷的名称。它的块设备在 /dev/mapper/ 中设置。<卷名> 在文件中的所有行中必须是唯一的。
• <uuid> 是包含加密数据的底层块设备的 UUID。
以下是 /etc/crypttab 中条目的两个示例
crypt_root UUID=b5600ed6-69e7-42b8-bee3-ecfdd12649d1
crypt_UDA UUID=cf6fa01d-1127-4612-9992-2f6db77385e0
如果您的设备有多个加密文件系统,则必须为您希望引导加载程序解锁的每个文件系统向 /etc/crypttab 添加一行。
以下是如何解锁加密文件系统的示例
1. 输入以下命令以查找加密文件系统的 UUID
$ sudo blkid | grep TYPE=\"crypto_LUKS\"
此命令行显示每个加密磁盘的输出行,其中包括磁盘的 UUID。这样的行如下所示
/dev/mmcblk0p43: UUID="5096aa4d-6590-429b-9295-a1fe041b8fa3" TYPE="crypto_LUKS" PARTLABEL="UDA" PARTUUID="2b23da7f-2f18-44bf-9e1d-6e3a3a39ad21"
3. 为您要在后续重新启动时解锁的每个加密文件系统向 /etc/crypttab 添加一行。该行指定要解锁的文件系统的 UUID,如以下示例所示
crypt_fs UUID="5096aa4d-6590-429b-9295-a1fe041b8fa3"
4. 重新打包 initrd 并将其存储在 /boot 中,替换最初存在的 initrd。
5. 在
/mnt 中创建一个子目录,initrd 可以在初始化过程中在其中挂载每个解锁的文件系统。为了简单起见,NVIDIA 建议为每个子目录指定与要挂载到该子目录的卷相同的名称。对于步骤
3 中指定的卷,子目录将命名为
crypt_fs $ mkdir -p /mnt/crypt_fs
引导加载程序会在每次后续重新启动时解锁您的加密文件系统。
摘要
下图显示了加密磁盘解锁过程的总体流程。
该过程的步骤为
1. nvluks-srv-app 从 Trusty luks-srv TA 查询基于硬件的密码短语,并将其传递给 cryptsetup 实用程序。
2. cryptsetup 使用密码短语解锁磁盘并解密磁盘加密密钥 (DEK)。
3. cryptsetup 调用 DMCrypt 内核模块并将密钥加载到内核中。DMCrypt 使用 tegra-crypto 驱动程序,该驱动程序使用安全引擎 (SE) 硬件进行数据加密和解密。
4. 后续磁盘 I/O 通过 DMCrypt 路由,DMCrypt 在读取和写入数据时对其进行解密和加密。
制造过程
下图说明了基于 Jetson 的设备的开发后制造过程
要刷写到生产设备上的所有磁盘映像和 blob 必须在安全系统上生成。HSM 拥有制造过程中所需的所有安全密钥。如果要一次刷写多个生产设备,则安全机器必须能够将映像部署到其他系统(刷写机器)以进行设备刷写。
如果设备设计为使用每设备加密磁盘映像,则必须在安全机器上一次生成一个映像。