# 启用安全功能的工作流程

[English]

## 概述

启用 ESP32 SoC 的安全功能时，建议保证不间断的电源供应。在此过程中，如果发生电力故障，可能会引起难以调试的问题，甚至在某些情况下可能导致永久性启动故障。

这份指南介绍了一系列工作流程，从而在外部主机的协助下启用设备的安全功能。这些工作流程分为多个阶段，每个阶段都会在主机上生成签名/加密密钥，从而在发生电力或其他故障时，提高恢复几率。此外，在主机的协助下，这些流程将加快整体配置过程（例如，在主机上加密固件要比在设备上加密更快）。

重要

可以在 QEMU 模拟器 中虚拟测试 ESP32-S3 目标芯片的安全功能。安全工作流程建立后，便可在真实硬件上继续操作。

## 目标

1. 用逐步指令简化启用安全功能的传统工作流程。

2. 设计比基于固件的传统工作流更加灵活的工作流。

3. 将工作流划分为多个小操作，从而提高可靠性。

4. 消除对 二级引导加载程序 的依赖。

## 准备工作

- `esptool`：确保已安装 `esptool`。可运行以下命令安装：

    pip install esptool

## 目录

- 外部启用 flash 加密和 Secure Boot v2

- 外部启用 flash 加密

- 外部启用 Secure Boot v2

- 启用外部 NVS 加密

## 启用安全功能

### 外部启用 flash 加密和 Secure Boot v2

重要

建议在生产用例中同时启用 flash 加密和 Secure Boot v2。

外部启用 flash 加密和 Secure Boot v2 时，须遵循以下启用顺序：

1. 按照 外部启用 flash 加密 中列出的步骤启用 flash 加密功能。

2. 按照 外部启用 Secure Boot v2 中列出的步骤启用 Secure Boot v2 功能。

须严格遵循以上顺序，因为启用 Secure Boot (SB) v2 时，要确保 SB v2 密钥可读。通过启用 `RD_DIS` (`ESP_EFUSE_WR_DIS_RD_DIS`) 的写保护，确保了密钥的可读性。但是，这也为启用 flash 加密带来了困难，因为 flash 加密 (FE) 密钥须保持不可读状态。产生这种冲突的原因是 `RD_DIS` 已受到写保护，因此无法对 FE 密钥进行读保护。

### 外部启用 flash 加密

在这种情况下，所有与 flash 加密相关的 eFuse 都是借助 espefuse 工具写入的。关于 flash 加密过程的详细信息，请参阅 flash 加密。

1. 确保有一块 ESP32-S3，其默认 flash 加密 eFuse 设置如 相关 eFuses 所示

   > 参考 ESP32-S3 flash 加密状态，查看 flash 加密状态。

   > 此时需要擦除芯片上的 flash，且 flash 加密必须尚未启用。请运行以下命令进行擦除：

   >     esptool.py --port PORT erase_flash

2. 生成一个 flash 加密密钥

   > 运行以下命令可以生成一个随机的 flash 加密密钥：

   > 如果 生成的 AES-XTS 密钥大小 为 AES-128（256 位密钥）：

   >     espsecure.py generate_flash_encryption_key my_flash_encryption_key.bin

   > 如果 生成的 AES-XTS 密钥的大小 为 AES-256（512 位密钥）：

   >     espsecure.py generate_flash_encryption_key --keylen 512 my_flash_encryption_key.bin

3. 将 flash 加密密钥烧录到 eFuse 中

   > 警告

   > 这个操作 无法回退。

   > 运行以下命令进行烧录：

   >     espefuse.py --port PORT burn_key BLOCK my_flash_encryption_key.bin KEYPURPOSE

   > 其中， `BLOCK` 是位于 `BLOCK_KEY0` 和 `BLOCK_KEY5` 之间的空闲密钥块， `KEYPURPOSE` 是 `XTS_AES_256_KEY_1`， `XTS_AES_256_KEY_2` 或 `XTS_AES_128_KEY`。有关密钥用途的说明，请参阅 ESP32-S3 技术参考手册。

   > 对于 AES-128（256 位密钥）- `XTS_AES_128_KEY`：

   >     espefuse.py --port PORT burn_key BLOCK my_flash_encryption_key.bin XTS_AES_128_KEY

   > 对于 AES-256（512 位密钥）- `XTS_AES_256_KEY_1` 和 `XTS_AES_256_KEY_2`。 `espefuse.py` 支持通过虚拟密钥用途 `XTS_AES_256_KEY` 将这两个密钥用途和一个 512 位密钥一起烧录到两个单独的密钥块中。使用时， `espefuse.py` 会把密钥的前 256 位烧录到指定的 `BLOCK`，并把相应块的密钥用途烧录为 `XTS_AES_256_KEY_1`。密钥的后 256 位会被烧录到 `BLOCK` 后的第一个空闲密钥块，相应块的密钥用途会烧录为 `XTS_AES_256_KEY_2`。

   >     espefuse.py --port PORT burn_key BLOCK my_flash_encryption_key.bin XTS_AES_256_KEY

   > 如果要指定使用两个块，那么可以将密钥分成两个 256 位密钥并手动烧录，以 `XTS_AES_256_KEY_1` 和 `XTS_AES_256_KEY_2` 作为密钥用途：

   >     split -b 32 my_flash_encryption_key.bin my_flash_encryption_key.bin
   >     espefuse.py --port PORT burn_key BLOCK my_flash_encryption_key.bin.aa XTS_AES_256_KEY_1
   >     espefuse.py --port PORT burn_key BLOCK+1 my_flash_encryption_key.bin.ab XTS_AES_256_KEY_2

   > 警告

   > 对于 ESP32-S3，XTS_AES 密钥不能使用 BLOCK9 (BLOCK_KEY5)。

4. 烧录 `SPI_BOOT_CRYPT_CNT` eFuse

   > 如果你只想在 开发 模式下启用 flash 加密，并在将来可能会禁用 flash 加密，可将下面命令中的 SPI_BOOT_CRYPT_CNT 值从 7 更新为 0x1。（不推荐在生产中使用）

   >     espefuse.py --port PORT --chip esp32s3 burn_efuse SPI_BOOT_CRYPT_CNT 7

5. 烧录下列与 flash 加密相关的安全 eFuse

   > 1. 烧录安全 eFuse

   > 重要

   > 对于生产用例，强烈建议烧录下列所有的 eFuse。

   > - `DIS_DOWNLOAD_ICACHE`：禁用 UART cache

   > - `DIS_DOWNLOAD_DCACHE`：禁用 UART cache

   > - `HARD_DIS_JTAG`：硬禁用 JTAG 外设

   > - `DIS_DIRECT_BOOT`：禁用直接引导（旧版 SPI 引导模式）

   > - `DIS_USB_JTAG`：禁止从 USB 切换到 JTAG

   > - `DIS_DOWNLOAD_MANUAL_ENCRYPT`：禁用 UART 引导加载程序加密访问

   > 可运行以下命令烧录相应的 eFuse：

   >     espefuse.py burn_efuse --port PORT EFUSE_NAME 0x1

   > 备注

   > 请将 `EFUSE_NAME` 更新为需要烧录的 eFuse。可以在上述命令中添加多个 efuse 同时进行烧录（例如：`EFUSE_NAME VAL EFUSE_NAME2 VAL2`）。有关 espefuse.py 的更多信息，请参阅 此文档。

   > 2. 对安全 eFuse 采用写保护

   > 在烧录相应 eFuse 后，需要对安全配置进行 write_protect。请烧录下列 eFuse：

   >     espefuse.py --port PORT write_protect_efuse DIS_ICACHE

   > 备注

   > 以上 eFuse 的写保护还对其他多个 eFuse 起效。详情请参阅 ESP32-S3 eFuse 表。

6. 配置项目

   > 项目的引导加载程序和应用程序二进制文件必须使用默认配置的 flash 加密量产模式进行构建。

   > 如下所示，可以在 menuconfig 中设置 flash 加密量产模式：

   > - 启动时启用 flash 加密。

   > - 选择量产模式 （注意，若选择量产模式，则将烧录 `EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT` eFuse 位，ROM 下载模式下 flash 加密硬件将被禁用）。

   > - 选择 UART ROM 下载模式（永久切换到安全模式（推荐））。这是推荐的默认选项，如果不需要，也可将其更改为永久禁用 UART ROM 下载模式。

   > - 选择适当的引导加载程序日志级别。

   > - 保存配置并退出。

7. 构建、加密并烧录二进制文件

   > 可以在主机上运行下列命令来加密二进制文件：

   >     espsecure.py encrypt_flash_data --aes_xts --keyfile my_flash_encryption_key.bin --address 0x0 --output bootloader-enc.bin build/bootloader/bootloader.bin

   >     espsecure.py encrypt_flash_data --aes_xts --keyfile my_flash_encryption_key.bin --address 0x8000 --output partition-table-enc.bin build/partition_table/partition-table.bin

   >     espsecure.py encrypt_flash_data --aes_xts --keyfile my_flash_encryption_key.bin --address 0x10000 --output my-app-enc.bin build/my-app.bin

   > 备注

   > 如果同时启用了安全启动功能，请先对固件进行安全启动签名，再执行上述加密操作。

   > 上述命令中的偏移量仅适用于示例固件，请通过检查分区表条目或运行 idf.py partition-table 来获取你固件的实际偏移量。请注意，不需要加密所有二进制文件，只需加密在分区表定义文件中带有 `encrypted` 标记的文件，其他二进制文件只作为构建过程的普通输出进行烧录。

   > 使用 `esptool.py` 可以将上述文件烧写到各自的偏移地址。要查看所有推荐的 `esptool.py` 命令行选项，请查阅 `idf.py build` 构建成功后打印的输出。

   > 若应用程序包含分区 `otadata` 和 `nvs_encryption_keys`，则该分区也需加密。详情请参阅 加密分区。

   > 备注

   > 如果 ESP32-S3 启动时无法识别烧录的密文，请检查密钥是否匹配、命令行参数是否精确匹配及偏移量的正确性。偏移量必须正确，因为当偏移量改变时，密文也会改变。

   > 使用 `espsecure.py decrypt_flash_data` 命令时，可以用相同的选项（和不同的输入或输出文件）来解密密文 flash 或之前加密的文件。

8. 确保 ROM 下载模式安全

   > 警告

   > 请在最后烧录以下位。烧录后，espefuse 工具将无法再用于烧录其他 eFuse。

   > 启用安全下载模式：

   > > - `ENABLE_SECURITY_DOWNLOAD`：启用安全 ROM 下载模式

   > > 运行以下指令，烧录 eFuse：

   > >     espefuse.py --port PORT burn_efuse ENABLE_SECURITY_DOWNLOAD

重要

9. 从主机上删除 flash 加密密钥

   > 一旦为设备启用了 flash 加密，密钥 必须立即删除。这能确保主机以后不为同一设备生成加密二进制文件，从而减少 flash 加密密钥漏洞。

#### flash 加密指南

- 建议为每个设备生成唯一的 flash 加密密钥用于生产用例。

- 确保主机用于生成 flash 加密密钥的 RNG 具有良好的熵。

- 更多详细信息请参阅 flash 加密的局限性。

### 外部启用 Secure Boot v2

在此工作流中，我们会使用 `espsecure` 工具生成签名密钥，并使用 `espefuse` 工具烧录相关 eFuse。关于 Secure Boot v2 流程的详细信息，请参阅 安全启动 (Secure Boot) v2。

1. 生成 Secure Boot v2 签名私钥

   > 运行以下命令可以生成 RSA3072 方案的 Secure Boot v2 签名密钥：

   >     espsecure.py generate_signing_key --version 2 --scheme rsa3072 secure_boot_signinig_key.pem

   > 每次可以在 Secure Boot v2 中使用 3 个密钥。这些密钥应独立计算，分开存储。同一个命令也可以使用不同的密钥文件名，生成多个 Secure Boot v2 签名密钥。建议使用多个密钥，以降低对单个密钥的依赖。

2. 生成公钥摘要

   > 运行以下命令可以为上一步生成的私钥生成公钥摘要：

   >     espsecure.py digest_sbv2_public_key --keyfile secure_boot_signing_key.pem --output digest.bin

   > 如果有多个摘要，应将每个摘要保存在一个单独的文件中。

3. 在 eFuse 中烧录密钥摘要

   > 运行以下命令可以在 eFuse 中烧录公钥摘要：

   >     espefuse.py --port PORT --chip esp32s3 burn_key BLOCK digest.bin SECURE_BOOT_DIGEST0

   > 其中，`BLOCK` 是 `BLOCK_KEY0` 和 `BLOCK_KEY5` 之间的一个空闲密钥块。

   > 如果有多个摘要，可以将密钥用途分别更改为 `SECURE_BOOT_DIGEST1` 和 `SECURE_BOOT_DIGEST2`，从而依次烧录其他摘要。

4. 启用 Secure Boot v2

   > 运行以下命令启用 Secure Boot v2 eFuse：

   >     espefuse.py --port PORT --chip esp32s3 burn_efuse SECURE_BOOT_EN

5. 烧录相关 eFuse

   > 1. 烧录安全 eFuse

   > 重要

   > 对于生产用例，强烈建议烧录下列所有 eFuse。

   > - `HARD_DIS_JTAG`：硬禁用 JTAG 外设。

   > - `SOFT_DIS_JTAG`：禁止软件对 JTAG 外设的访问。

   > - `DIS_DIRECT_BOOT`: 禁用直接引导（旧版 SPI 引导模式）。

   > - `DIS_USB_JTAG`：禁止从 USB 切换到 JTAG。

   > - `SECURE_BOOT_AGGRESSIVE_REVOKE`：主动吊销密钥摘要。详请请参阅 激进方法。

   > 运行以下命令烧录相应的 eFuse：

   >     espefuse.py burn_efuse --port PORT EFUSE_NAME 0x1

   > 备注

   > 请将 EFUSE_NAME 更新为需烧录的 eFuse。在上述命令中添加多个 eFuse 可以同时烧录（例如：EFUSE_NAME VAL EFUSE_NAME2 VAL2）。有关 espefuse.py 的更多信息，请参阅 此文档。

   > 2. 与 Secure Boot v2 相关的 eFuse

   > 1. 禁用读保护选项：

   > 在 eFuse 中烧录的 Secure Boot 摘要必须保持可读，否则会导致安全启动失败。烧录以下 eFuse 可防止意外启用此密钥块的读保护：

   >     espefuse.py -p $ESPPORT write_protect_efuse RD_DIS

   > 重要

   > 烧录此 eFuse 后，不能为任何密钥启用读保护。例如，如果此时需要对密钥进行读保护的 flash 加密尚未启用，则之后也无法启用。请确保在此之后没有其他 efuse 密钥需要读保护。

   > 2. 吊销密钥摘要：

   > 在我们烧录 Secure Boot 密钥时，需要吊销未使用的摘要槽。可以通过运行以下命令吊销相应的槽：

   >     espefuse.py --port PORT --chip esp32s3 burn_efuse EFUSE_REVOKE_BIT

   > 上述命令中的 `EFUSE_REVOKE_BIT` 可以是 `SECURE_BOOT_KEY_REVOKE0` 或 `SECURE_BOOT_KEY_REVOKE1` 或 `SECURE_BOOT_KEY_REVOKE2`。注意，只有未使用的密钥摘要必须吊销。一旦吊销，相应的摘要就不能再次使用。

6. 构建二进制文件

   > 默认情况下，一级 (ROM) 引导加载程序只会验证 二级引导加载程序。只有在启用 CONFIG_SECURE_BOOT 选项（并将 CONFIG_SECURE_BOOT_VERSION 设置为 `SECURE_BOOT_V2_ENABLED`）时，二级引导加载程序才会在构建引导加载程序时验证应用程序分区。

   > 1. 打开 Editing the Configuration，在 `Security features` 中设置 `Enable hardware Secure Boot in bootloader` 启用 Secure Boot。

   > 选中 `Secure Boot v2` 选项， `App Signing Scheme` 将被默认设置为 RSA。

   > 2. 在 Editing the Configuration 中为项目禁用 CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES 选项，以确保所有生成的二进制文件都受到安全保护且未签名，避免生成签名的二进制文件，因为需要使用 `espsecure` 工具手动签名二进制文件。

7. 构建、签名并烧录二进制文件

   > 完成上述配置后，可以用 `idf.py build` 命令构建引导加载程序和应用程序二进制文件。

   > Secure Boot v2 工作流程只验证 `bootloader` 和 `application` 二进制文件，因此只需要对这些二进制文件进行签名。其他二进制文件（例如 `partition-table.bin`）可以在构建后直接进行烧录。

   > 运行以下命令对 `bootloader.bin` 和 `app.bin` 二进制文件进行签名：

   >     espsecure.py sign_data --version 2 --keyfile secure_boot_signing_key.pem --output bootloader-signed.bin build/bootloader/bootloader.bin

   >     espsecure.py sign_data --version 2 --keyfile secure_boot_signing_key.pem --output my-app-signed.bin build/my-app.bin

   > 如要使用多个安全引导密钥，可在同一个已签名二进制文件中添加用新密钥签名的块，如下所示：

   >     espsecure.py sign_data --keyfile secure_boot_signing_key2.pem --version 2 --amend_signatures -o bootloader-signed.bin bootloader-signed.bin

   >     espsecure.py sign_data --keyfile secure_boot_signing_key2.pem --version 2 --apend_signatures -o my-app-signed.bin my-app-signed.bin

   > 如果有第三个密钥，则可以重复以上过程。注意：输入和输出文件不能用相同名字来命名。

   > 运行以下命令来检查附加到二进制文件的签名：

   >     espsecure.py signature_info_v2 bootloader-signed.bin

   > 然后使用 `esptool.py` 将上述文件和其他二进制文件（如分区表）烧录到各自的偏移地址。要查看所有推荐的 `esptool.py` 命令行选项，请参阅 `idf.py build` 的输出结果。要获得固件的 flash 偏移地址，可查找分区表条目或运行 `idf.py partition-table` 查看。

8. 确保 ROM 下载模式安全

   > 警告

   > 请在最后烧录以下位。烧录后，espefuse 工具将无法再用于烧录其他 eFuse。

   > 启用安全下载模式：

   > - `ENABLE_SECURITY_DOWNLOAD`：启用安全 ROM 下载模式

   > 运行以下指令，烧录 eFuse：

   >     espefuse.py --port PORT burn_efuse ENABLE_SECURITY_DOWNLOAD

#### Secure Boot v2 指南

- 建议将 Secure Boot 密钥存储在高度安全的地方，如可以使用物理或云 HSM 来存储 Secure Boot 私钥。请参阅 远程镜像签名 获取更多详细信息。

- 建议使用所有可用的摘要槽，降低对单个私钥的依赖。

### 启用外部 NVS 加密

有关 NVS 加密及相关方案的详细信息，请参阅 NVS 加密。

#### 基于 HMAC 启用 NVS 加密

1. 生成 HMAC 密钥和 NVS 加密密钥

   > 在基于 HMAC 的 NVS 加密方案中，有两个密钥：

   > - HMAC 密钥 - 256 位的 HMAC 密钥，应存储在 eFuse 中。

   > - NVS 加密密钥 - 用于加密 NVS 分区，在命令运行时通过 HMAC 密钥派生。

   > 使用以下命令，通过 nvs_flash/nvs_partition_generator/nvs_partition_gen.py 脚本可以生成上述密钥：

   >     python3 nvs_partition_gen.py generate-key --key_protect_hmac --kp_hmac_keygen --kp_hmac_keyfile hmac_key.bin --keyfile nvs_encr_key.bin

   > 运行上述命令后，`keys` 文件夹下会生成相应的密钥。

2. 在 eFuse 中烧录 HMAC 密钥

   > 使用以下命令在 ESP32-S3 的 eFuse 中烧录 NVS 密钥：

   >     espefuse.py --port PORT burn_key BLOCK hmac_key.bin HMAC_UP

   > 其中，`BLOCK` 是 `BLOCK_KEY0` 和 `BLOCK_KEY5` 之间的一个空闲密钥块。

3. 生成加密的 NVS 分区

   > 主机上将会生成加密 NVS 分区。有关生成加密 NVS 分区的详细信息，请参阅读 生成 NVS 加密分区。为此，CSV 文件中应该包含 NVS 文件的全部内容。详情请参阅 CSV 文件格式。

   > 使用以下命令，可以生成加密的 NVS 分区：

   >     python3 nvs_partition_gen.py encrypt sample_singlepage_blob.csv nvs_encr_partition.bin 0x3000 --inputkey keys/nvs_encr_key.bin

   > 下面解释一些命令参数：

   > - CSV 文件名 - 此命令中，`sample_singlepage_blob.csv` 是指包含 NVS 数据的 CSV 文件，请将其替换为所选择的文件。

   > - NVS 分区大小 - 这是 NVS 分区的大小（以字节为单位）。请将上述命令中的示例值 `0x3000` 更新为你实际 NVS 分区的正确大小。

4. 配置项目

   > - 通过设置 CONFIG_NVS_ENCRYPTION，启用 NVS 加密。

   > - 将 CONFIG_NVS_SEC_KEY_PROTECTION_SCHEME 设置为 `CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC`，启用基于 HMAC 的 NVS 加密。

   > - 通过设置 CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID，将 HMAC eFuse 密钥 ID 设为步骤 2 中烧录 eFuse 密钥的 ID。

5. 烧录 NVS 分区

   > 使用 `esptool.py` 命令，将步骤 3 中生成的 NVS 分区 (`nvs_encr_partition.bin`) 烧录到相应的偏移地址。要查看所有推荐的 `esptool.py` 命令行选项，请查阅 `idf.py build` 构建成功后打印的输出。

   > 如果芯片启用了 flash 加密，请先加密分区再进行烧录。详情请参阅 flash 加密工作流程 的相关烧录步骤。

#### 基于 flash 加密启用 NVS 加密

在这种情况下，主机上生成 NVS 加密密钥，并将其烧录到芯片上，借助 flash 加密 功能进行保护。

1. 生成 NVS 加密密钥

   > 使用 NVS 分区生成工具，可以生成相应的密钥。在主机上生成加密密钥，并将该密钥以加密状态存储在 ESP32-S3 的 flash 中。

   > 使用以下命令，通过 nvs_flash/nvs_partition_generator/nvs_partition_gen.py 脚本生成密钥：

   >     python3 nvs_partition_gen.py generate-key --keyfile nvs_encr_key.bin

   > `keys` 文件夹中将生成相应的密钥。

2. 生成加密的 NVS 分区

   > 在主机上生成实际的加密 NVS 分区，详情请参阅 生成 NVS 加密分区。为此，CSV 文件应包含 NVS 文件数据，详情请参阅 CSV 文件格式。

   > 使用以下命令，可以生成加密的 NVS 分区：

   >     python3 nvs_partition_gen.py encrypt sample_singlepage_blob.csv nvs_encr_partition.bin 0x3000 --inputkey keys/nvs_encr_key.bin

   > 下文解释了上述命令中的一些参数：

   > - CSV 文件名 - 上述命名中的 sample_singlepage_blob.csv 是指包含 NVS 数据的 CSV 文件，请将其替换为所选文件。

   > - NVS 分区大小 - 这是 NVS 分区的大小（以字节为单位）。请将上述命令中的示例值 `0x3000` 更新为你实际 NVS 分区的正确大小。

3. 配置项目

   > - 通过启用 CONFIG_NVS_ENCRYPTION 来启用 NVS 加密。

   > - 通过将 CONFIG_NVS_SEC_KEY_PROTECTION_SCHEME 设置为 `CONFIG_NVS_SEC_KEY_PROTECT_USING_FLASH_ENC`，配置 NVS 使用基于 flash 加密的方案。

4. 烧录 NVS 分区和 NVS 加密密钥

   > 使用 `esptool.py` 命令，将 NVS 分区 (`nvs_encr_partition.bin`) 和 NVS 加密密钥 (`nvs_encr_key.bin`) 烧录到各自的偏移地址。通过 `idf.py build` 成功后打印的输出，可查看所有推荐的 `esptool.py` 命令行选项。

   > 若芯片启用了 flash 加密，请在烧录前先对 NVS 加密密钥分区进行加密。详情请参阅 flash 加密工作流程 中与烧录相关的步骤。

此文档对您有帮助吗？

- 反馈已收到，谢谢！
- 如果您有其他意见，欢迎填写乐鑫文档反馈表。

- 我们重视您的反馈。
- 您可以填写乐鑫文档反馈表告诉我们如何改进该文档。