..
  WARNING: The steps of each section in the document are referenced at multiple places. If you are changing the step number by adding/deleting a step then make sure to update the references respectively.

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

:link_to_translation:`en:[English]`

{IDF_TARGET_CRYPT_CNT:default="SPI_BOOT_CRYPT_CNT",esp32="FLASH_CRYPT_CNT"}
{IDF_TARGET_CRYPT_CNT_MAX_VAL:default="7",esp32="127"}
{IDF_TARGET_SBV2_DEFAULT_SCHEME:default="RSA", esp32c2="ECDSA (V2)"}
{IDF_TARGET_FLASH_ENC_ARGS:default="--aes-xts", esp32=""}

概述
----

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

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

.. only:: TARGET_SUPPORT_QEMU

   .. important::

      可以在 :doc:`../api-guides/tools/qemu` 中虚拟测试 {IDF_TARGET_NAME} 目标芯片的安全功能。安全工作流程建立后，便可在真实硬件上继续操作。

目标
----

#. 用逐步指令简化启用安全功能的传统工作流程。
#. 设计比基于固件的传统工作流更加灵活的工作流。
#. 将工作流划分为多个小操作，从而提高可靠性。
#. 消除对 :ref:`second-stage-bootloader` 的依赖。

准备工作
--------

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

.. code:: bash

    pip install esptool

目录
----

* :ref:`enable-flash-encryption-and-secure-boot-v2-externally`
* :ref:`enable-flash-encryption-externally`
* :ref:`enable-secure-boot-v2-externally`
* :ref:`enable-nvs-encryption-externally`

启用安全功能
------------

.. _enable-flash-encryption-and-secure-boot-v2-externally:

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

.. important::

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

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

#. 按照 :ref:`enable-flash-encryption-externally` 中列出的步骤启用 flash 加密功能。
#. 按照 :ref:`enable-secure-boot-v2-externally` 中列出的步骤启用 Secure Boot v2 功能。

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

.. _enable-flash-encryption-externally:

外部启用 flash 加密
^^^^^^^^^^^^^^^^^^^

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

1. 确保有一块 {IDF_TARGET_NAME}，其默认 flash 加密 eFuse 设置如 :ref:`flash-encryption-efuse` 所示

    参考 :ref:`flash-encryption-status`，查看 flash 加密状态。

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

    .. code:: bash

        esptool --port PORT erase-flash

2. 生成一个 flash 加密密钥

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

    .. only:: not SOC_FLASH_ENCRYPTION_XTS_AES

        .. code-block:: bash

            espsecure generate-flash-encryption-key my_flash_encryption_key.bin

    .. only:: SOC_FLASH_ENCRYPTION_XTS_AES_256

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

        .. code-block:: bash

            espsecure generate-flash-encryption-key my_flash_encryption_key.bin

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

        .. code-block:: bash

            espsecure generate-flash-encryption-key --keylen 512 my_flash_encryption_key.bin


    .. only:: SOC_FLASH_ENCRYPTION_XTS_AES_128 and not SOC_FLASH_ENCRYPTION_XTS_AES_256 and not SOC_EFUSE_CONSISTS_OF_ONE_KEY_BLOCK

        .. code-block:: bash

            espsecure generate-flash-encryption-key my_flash_encryption_key.bin

    .. only:: SOC_FLASH_ENCRYPTION_XTS_AES_128 and SOC_EFUSE_CONSISTS_OF_ONE_KEY_BLOCK

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

        .. code-block:: bash

            espsecure generate-flash-encryption-key my_flash_encryption_key.bin

        如果 :ref:`生成的 AES-XTS 密钥的大小 <CONFIG_SECURE_FLASH_ENCRYPTION_KEYSIZE>` 是从 128 位（SHA256（128 位））派生的 AES-128 密钥：

        .. code-block:: bash

            espsecure generate-flash-encryption-key --keylen 128 my_flash_encryption_key.bin

3. 将生成的 flash 加密密钥烧录到设备中

    .. only:: SOC_KEY_MANAGER_SUPPORTED

        a. 若使用密钥管理器存储 flash 加密密钥，请为 flash 加密密钥生成密钥恢复信息，并使用以下命令将其保存至 flash 地址 0x0 处：

        .. code-block:: bash

            esptool --port PORT write-flash 0x0 key_recovery_info.bin

        将密钥恢复信息存储至 flash 后，还需要烧录 ``KM_XTS_KEY_LENGTH_256`` 和 ``FORCE_USE_KEY_MANAGER_KEY`` eFuse。

        .. warning::

            此操作 **无法回退**。

        ``FORCE_USE_KEY_MANAGER_KEY`` eFuse 的第 1 位用于强制使用基于密钥管理器的 XTS-AES 密钥。

        .. code-block:: bash

            espefuse --port PORT burn-efuse FORCE_USE_KEY_MANAGER_KEY 2

        ``KM_XTS_KEY_LENGTH_256`` eFuse 用于控制基于密钥管理器的 XTS-AES 密钥长度。将该 eFuse 设置为 1 表示使用 128 位密钥，设置为 0 表示使用 256 位密钥。

        若使用 128 位密钥，请将 ``KM_XTS_KEY_LENGTH_256`` eFuse 设置为 1。

        .. code-block:: bash

            espefuse --port PORT burn-efuse KM_XTS_KEY_LENGTH_256 1

        若使用 256 位密钥，请将 ``KM_XTS_KEY_LENGTH_256`` eFuse 设置为 0。

        .. code-block:: bash

            espefuse --port PORT burn-efuse KM_XTS_KEY_LENGTH_256 0

        b. 如需将 flash 加密密钥存储至 eFuse 中，请运行以下命令：

    .. only:: not SOC_KEY_MANAGER_SUPPORTED

        如需将 flash 加密密钥存储在 eFuse 中，请运行以下命令：

    .. warning::

        此操作 **无法回退**。

    .. only:: not SOC_FLASH_ENCRYPTION_XTS_AES

        .. code-block:: bash

            espefuse --port PORT burn-key flash_encryption my_flash_encryption_key.bin

    .. only:: SOC_FLASH_ENCRYPTION_XTS_AES_256

        .. code-block:: bash

            espefuse --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``。有关密钥用途的说明，请参阅 `{IDF_TARGET_NAME} 技术参考手册 <{IDF_TARGET_TRM_CN_URL}>`__。

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

        .. code-block:: bash

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

        .. code-block:: bash

            espefuse --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`` 作为密钥用途：

        .. code-block:: bash

            split -b 32 my_flash_encryption_key.bin my_flash_encryption_key.bin
            espefuse --port PORT burn-key BLOCK my_flash_encryption_key.bin.aa XTS_AES_256_KEY_1
            espefuse --port PORT burn-key BLOCK+1 my_flash_encryption_key.bin.ab XTS_AES_256_KEY_2


    .. only:: SOC_FLASH_ENCRYPTION_XTS_AES_128 and not SOC_FLASH_ENCRYPTION_XTS_AES_256 and not SOC_EFUSE_CONSISTS_OF_ONE_KEY_BLOCK

        .. code-block:: bash

            espefuse --port PORT burn-key BLOCK my_flash_encryption_key.bin XTS_AES_128_KEY

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

    .. only:: SOC_FLASH_ENCRYPTION_XTS_AES_128 and SOC_EFUSE_CONSISTS_OF_ONE_KEY_BLOCK

        对于 AES-128 (256 位密钥) - ``XTS_AES_128_KEY`` (``XTS_KEY_LENGTH_256`` eFuse 会被烧录为 1)：

        .. code-block:: bash

            espefuse --port PORT burn-key BLOCK_KEY0 flash_encryption_key256.bin XTS_AES_128_KEY

        对于从 SHA256（128 eFuse 位）派生的 AES-128 密钥 - ``XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS``。FE 密钥会被写入 eFuse BLOCK_KEY0 低位部分。高 128 位不会被使用，并保持可供软件读取状态。使用 espefuse 工具的特殊模式，可以用任何 espefuse 命令将数据写入其中，可参考下文 ``同时烧录两个密钥``。

        .. code-block:: bash

            espefuse --port PORT burn-key BLOCK_KEY0 flash_encryption_key128.bin XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS

        同时烧录两个密钥（Secure Boot 和 flash 加密）：

        .. code-block:: bash

            espefuse --port PORT --chip esp32c2 burn-key-digest secure_boot_signing_key.pem \
                                                burn-key BLOCK_KEY0 flash_encryption_key128.bin XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS


    .. only:: SOC_EFUSE_BLOCK9_KEY_PURPOSE_QUIRK

        .. warning::

            对于 {IDF_TARGET_NAME}，XTS_AES 密钥不能使用 BLOCK9 (BLOCK_KEY5)。


4. 烧录 ``{IDF_TARGET_CRYPT_CNT}`` eFuse

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

    .. code-block:: bash

        espefuse --port PORT --chip {IDF_TARGET_PATH_NAME} burn-efuse {IDF_TARGET_CRYPT_CNT} {IDF_TARGET_CRYPT_CNT_MAX_VAL}

    .. only:: esp32

        在使用 {IDF_TARGET_NAME} 时，还要烧录 ``FLASH_CRYPT_CONFIG``。通过运行以下命令进行烧录：

        .. code-block:: bash

            espefuse --port PORT --chip {IDF_TARGET_PATH_NAME} burn-efuse FLASH_CRYPT_CONFIG 0xF

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

    A) 烧录安全 eFuse

    .. important::

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

    .. list::

        :esp32: - ``DISABLE_DL_ENCRYPT``：禁用 UART 引导加载程序加密访问。
        :esp32: - ``DISABLE_DL_DECRYPT``：禁用 UART 引导加载程序解密访问。
        :esp32: - ``DISABLE_DL_CACHE``：禁用 UART 引导加载程序 flash cache 访问
        :esp32: - ``JTAG_DISABLE``：禁用 JTAG
        :SOC_EFUSE_DIS_BOOT_REMAP: - ``DIS_BOOT_REMAP``：禁用将 ROM 映射到 RAM 地址空间的功能
        :SOC_EFUSE_DIS_DOWNLOAD_ICACHE: - ``DIS_DOWNLOAD_ICACHE``：禁用 UART cache
        :SOC_EFUSE_DIS_DOWNLOAD_DCACHE: - ``DIS_DOWNLOAD_DCACHE``：禁用 UART cache
        :SOC_EFUSE_HARD_DIS_JTAG: - ``HARD_DIS_JTAG``：硬禁用 JTAG 外设
        :SOC_EFUSE_DIS_DIRECT_BOOT: - ``DIS_DIRECT_BOOT``：禁用直接引导（旧版 SPI 引导模式）
        :SOC_EFUSE_DIS_LEGACY_SPI_BOOT: - ``DIS_LEGACY_SPI_BOOT``：禁用旧版 SPI 引导模式
        :SOC_EFUSE_DIS_USB_JTAG: - ``DIS_USB_JTAG``：禁止从 USB 切换到 JTAG
        :SOC_EFUSE_DIS_PAD_JTAG: - ``DIS_PAD_JTAG``：永久禁用 JTAG
        :not esp32: - ``DIS_DOWNLOAD_MANUAL_ENCRYPT``：禁用 UART 引导加载程序加密访问
        :SOC_EFUSE_DIS_DOWNLOAD_MSPI: - ``DIS_DOWNLOAD_MSPI``：禁用下载模式下的 MSPI 访问
        :SOC_FLASH_ENCRYPTION_XTS_AES_SUPPORT_PSEUDO_ROUND: - ``XTS_DPA_PSEUDO_LEVEL``：启用 XTS-AES 外设的伪轮次功能。要烧录到 eFuse 中的值可以是 1、2 或 3，表示安全等级。默认情况下，ESP-IDF 的引导加载程序在启动过程中启用 flash 加密的量产模式时，会将该 eFuse 的值配置为 1。

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

    .. code:: bash

        espefuse --port PORT burn-efuse EFUSE_NAME 0x1

    .. note::

        请将 ``EFUSE_NAME`` 更新为需要烧录的 eFuse。可以在上述命令中添加多个 efuse 同时进行烧录（例如：``EFUSE_NAME VAL EFUSE_NAME2 VAL2``）。有关 `espefuse` 的更多信息，请参阅 `此文档 <https://docs.espressif.com/projects/esptool/en/latest/esp32/espefuse/index.html>`__。

    .. only:: esp32

        B) 对安全 eFuse 采用写保护

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

        .. code:: bash

            espefuse --port PORT write-protect-efuse DIS_CACHE

        .. note::

            以上 eFuse 的写保护还对其他多个 eFuse 起效。详情请参阅 {IDF_TARGET_NAME} eFuse 表。

    .. only:: SOC_EFUSE_DIS_ICACHE

        B) 对安全 eFuse 采用写保护

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

        .. code:: bash

            espefuse --port PORT write-protect-efuse DIS_ICACHE

        .. note::

            以上 eFuse 的写保护还对其他多个 eFuse 起效。详情请参阅 {IDF_TARGET_NAME} eFuse 表。

6. 配置项目

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

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

    .. list::

        - :ref:`启动时启用 flash 加密 <CONFIG_SECURE_FLASH_ENC_ENABLED>`。
        :esp32: - :ref:`选择量产模式 <CONFIG_SECURE_FLASH_ENCRYPTION_MODE>` （注意，若选择量产模式，则将烧录 ``DISABLE_DL_ENCRYPT`` 和 ``DISABLE_DL_DECRYPT`` eFuse 位，ROM 下载模式下 flash 加密硬件将被禁用）。
        :esp32: - :ref:`选择 UART ROM 下载模式（永久禁用（推荐））<CONFIG_SECURE_UART_ROM_DL_MODE>` （注意，此选项仅在 :ref:`CONFIG_ESP32_REV_MIN` 设为 3 (ESP32 V3) 时可用）。UART ROM 下载模式在默认设置中自动启用，但建议永久禁用此模式以减少攻击者可用的选项。
        :not esp32: - :ref:`选择量产模式 <CONFIG_SECURE_FLASH_ENCRYPTION_MODE>` （注意，若选择量产模式，则将烧录 ``EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT`` eFuse 位，ROM 下载模式下 flash 加密硬件将被禁用）。
        :not esp32: - :ref:`选择 UART ROM 下载模式（永久切换到安全模式（推荐））<CONFIG_SECURE_UART_ROM_DL_MODE>`。这是推荐的默认选项，如果不需要，也可将其更改为永久禁用 UART ROM 下载模式。
        - :ref:`选择适当的引导加载程序日志级别 <CONFIG_BOOTLOADER_LOG_LEVEL>`。
        - 保存配置并退出。

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

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

    .. code-block:: bash

        espsecure encrypt-flash-data {IDF_TARGET_FLASH_ENC_ARGS} --keyfile my_flash_encryption_key.bin --address {IDF_TARGET_CONFIG_BOOTLOADER_OFFSET_IN_FLASH} --output bootloader-enc.bin build/bootloader/bootloader.bin

        espsecure encrypt-flash-data {IDF_TARGET_FLASH_ENC_ARGS} --keyfile my_flash_encryption_key.bin --address 0x8000 --output partition-table-enc.bin build/partition_table/partition-table.bin

        espsecure encrypt-flash-data {IDF_TARGET_FLASH_ENC_ARGS} --keyfile my_flash_encryption_key.bin --address 0x10000 --output my-app-enc.bin build/my-app.bin

    .. note::

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

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

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

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

    .. note::

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

        .. only:: esp32

            如果 ESP32 在 eFuse 中使用非默认的 :ref:`FLASH_CRYPT_CONFIG 值 <setting-flash-crypt-config>`，需要将 ``--flash-crypt-conf`` 参数传递给 ``espsecure`` 以设置匹配值。如果通过二级引导加载程序配置 flash 加密，则不会发生这种情况，但是如果手动烧录了 eFuses 启用 flash 加密，就有可能发生。

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

8. 确保 ROM 下载模式安全

    .. warning::

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

    .. only:: esp32

        禁用 UART ROM DL 模式：

            .. list::

                - ``UART_DOWNLOAD_DIS``：禁用 UART ROM 下载模式

                运行以下指令，烧录 eFuse：

                .. code:: bash

                    espefuse --port PORT burn-efuse UART_DOWNLOAD_DIS

    .. only:: not esp32

        启用安全下载模式：

            .. list::

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

                运行以下指令，烧录 eFuse：

                .. code:: bash

                    espefuse --port PORT burn-efuse ENABLE_SECURITY_DOWNLOAD

.. important::

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

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

flash 加密指南
~~~~~~~~~~~~~~

* 建议为每个设备生成唯一的 flash 加密密钥用于生产用例。
* 确保主机用于生成 flash 加密密钥的 RNG 具有良好的熵。
* 更多详细信息请参阅 :ref:`flash-encryption-limitations`。

.. _enable-secure-boot-v2-externally:

外部启用 Secure Boot v2
^^^^^^^^^^^^^^^^^^^^^^^

在此工作流中，我们会使用 ``espsecure`` 工具生成签名密钥，并使用 ``espefuse`` 工具烧录相关 eFuse。关于 Secure Boot v2 流程的详细信息，请参阅 :doc:`/security/secure-boot-v2`。

1. 生成 Secure Boot v2 签名私钥

    .. only:: esp32 or SOC_SECURE_BOOT_V2_RSA

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

        .. code:: bash

            espsecure generate-signing-key --version 2 --scheme rsa3072 secure_boot_signing_key.pem

    .. only:: SOC_SECURE_BOOT_V2_ECC

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

        .. code:: bash

            espsecure generate-signing-key --version 2 --scheme ecdsa256 secure_boot_signing_key.pem

        .. only:: SOC_ECDSA_SUPPORT_CURVE_P384

           将上述命令中的方案更改为 ``ecdsa384``，可生成 ecdsa384 私钥。

    .. only:: SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS

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

2. 生成公钥摘要

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

    .. code:: bash

        espsecure digest-sbv2-public-key --keyfile secure_boot_signing_key.pem --output digest.bin

    .. only:: SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS

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

3. 在 eFuse 中烧录密钥摘要

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

    .. only:: esp32

        .. code:: bash

            espefuse --port PORT --chip esp32 burn-key secure_boot_v2 digest.bin

    .. only:: esp32c2

        .. code:: bash

            espefuse --port PORT --chip esp32c2 burn-key KEY_BLOCK0 digest.bin SECURE_BOOT_DIGEST

    .. only:: SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS

        .. code:: bash

            espefuse --port PORT --chip {IDF_TARGET_PATH_NAME} 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：

    .. only:: esp32

        .. code:: bash

            espefuse --port PORT --chip esp32 burn-efuse ABS_DONE_1

    .. only:: not esp32

        .. code:: bash

            espefuse --port PORT --chip {IDF_TARGET_PATH_NAME} burn-efuse SECURE_BOOT_EN

    .. only:: SOC_ECDSA_SUPPORT_CURVE_P384

        如果启用了带有 ECDSA-P384 签名方案的 Secure Boot v2，则必须使用 SHA-384 来计算镜像的摘要。因此，需要烧录以下 eFuse：

        .. code:: bash

            espefuse --port PORT --chip {IDF_TARGET_PATH_NAME} burn-efuse SECURE_BOOT_SHA384_EN


5. 烧录相关 eFuse

    A) 烧录安全 eFuse

    .. important::

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

    .. list::

        :esp32: - ``JTAG_DISABLE``：禁用 JTAG。
        :SOC_EFUSE_DIS_BOOT_REMAP: - ``DIS_BOOT_REMAP``：禁用将 ROM 重新映射到 RAM 地址空间的功能。
        :SOC_EFUSE_HARD_DIS_JTAG: - ``HARD_DIS_JTAG``：硬禁用 JTAG 外设。
        :SOC_EFUSE_SOFT_DIS_JTAG: - ``SOFT_DIS_JTAG``：禁止软件对 JTAG 外设的访问。
        :SOC_EFUSE_DIS_DIRECT_BOOT: - ``DIS_DIRECT_BOOT``: 禁用直接引导（旧版 SPI 引导模式）。
        :SOC_EFUSE_DIS_LEGACY_SPI_BOOT: - ``DIS_LEGACY_SPI_BOOT``：禁用旧版 SPI 引导模式。
        :SOC_EFUSE_DIS_USB_JTAG: - ``DIS_USB_JTAG``：禁止从 USB 切换到 JTAG。
        :SOC_EFUSE_DIS_PAD_JTAG: - ``DIS_PAD_JTAG``：永久禁用 JTAG。
        :SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS: - ``SECURE_BOOT_AGGRESSIVE_REVOKE``：主动吊销密钥摘要。详请请参阅 :ref:`secure-boot-v2-aggressive-key-revocation`。
        :SOC_ECDSA_P192_CURVE_DEFAULT_DISABLED: - ``WR_DIS_ECDSA_CURVE_MODE``：禁止写入 ECDSA 曲线模式的 eFuse 位。由于此写保护位与 ``ECC_FORCE_CONST_TIME`` 共享，建议配置好 ``ECC_FORCE_CONST_TIME`` eFuse 字段后，再设置此写保护位。
        :SOC_ECDSA_SUPPORT_CURVE_P384: - ``WR_DIS_SECURE_BOOT_SHA384_EN``：禁止写入 SHA-384 Secure Boot 的 eFuse 位。由于此写保护位与 ``XTS_DPA_PSEUDO_LEVEL`` 和 ``ECC_FORCE_CONST_TIME`` 共享，建议先配置好这两个 eFuse，再设置此写保护位。

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

    .. code:: bash

        espefuse --port PORT burn-efuse EFUSE_NAME 0x1

    .. note::

        请将 EFUSE_NAME 更新为需烧录的 eFuse。在上述命令中添加多个 eFuse 可以同时烧录（例如：EFUSE_NAME VAL EFUSE_NAME2 VAL2）。有关 `espefuse` 的更多信息，请参阅 `此文档 <https://docs.espressif.com/projects/esptool/en/latest/esp32/espefuse/index.html>`__。

    B) 与 Secure Boot v2 相关的 eFuse

    i) 禁用读保护选项：

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

    .. code:: bash

        espefuse -p $ESPPORT write-protect-efuse RD_DIS

    .. important::

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

    .. only:: SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS

        ii) 吊销密钥摘要：

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

        .. code:: bash

            espefuse --port PORT --chip {IDF_TARGET_PATH_NAME} burn-efuse EFUSE_REVOKE_BIT

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

6. 构建二进制文件

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

    A) 打开 :ref:`project-configuration-menu`，在 ``Security features`` 中设置 ``Enable hardware Secure Boot in bootloader`` 启用 Secure Boot。

    .. only:: esp32

        对于 ESP32，Secure Boot v2 仅适用于 ESP32 ECO3 及以上版本。要查看 ``Secure Boot v2`` 选项，芯片版本应更改为 v3.0 (ECO3)。要更改芯片版本，请在 ``Component Config -> Hardware Settings -> Chip Revision`` 中将 ``Minimum Supported ESP32 Revision`` 设置为 ``Rev 3.0 (ECO3)``。

    .. only:: SOC_SECURE_BOOT_V2_RSA or SOC_SECURE_BOOT_V2_ECC

        选中 ``Secure Boot v2`` 选项， ``App Signing Scheme`` 将被默认设置为 {IDF_TARGET_SBV2_DEFAULT_SCHEME}。

    B) 在 :ref:`project-configuration-menu` 中为项目禁用 :ref:`CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES` 选项，以确保所有生成的二进制文件都受到安全保护且未签名，避免生成签名的二进制文件，因为需要使用 ``espsecure`` 工具手动签名二进制文件。

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

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

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

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

    .. code:: bash

        espsecure sign-data --version 2 --keyfile secure_boot_signing_key.pem --output bootloader-signed.bin build/bootloader/bootloader.bin

        espsecure sign-data --version 2 --keyfile secure_boot_signing_key.pem --output my-app-signed.bin build/my-app.bin

    .. only:: SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS

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

        .. code:: bash

            espsecure sign-data --keyfile secure_boot_signing_key2.pem --version 2 --append-signatures -o bootloader-signed.bin bootloader-signed.bin

            espsecure sign-data --keyfile secure_boot_signing_key2.pem --version 2 --append-signatures -o my-app-signed.bin my-app-signed.bin

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

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

    .. code:: bash

        espsecure signature-info-v2 bootloader-signed.bin

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

8. 确保 ROM 下载模式安全

    .. warning::

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

    .. only:: esp32

        禁用 UART ROM DL 模式：

        .. list::

            - ``UART_DOWNLOAD_DIS``：禁用 UART ROM 下载模式

            运行以下指令，烧录 eFuse：

            .. code:: bash

                espefuse --port PORT burn-efuse UART_DOWNLOAD_DIS

    .. only:: not esp32

        启用安全下载模式：

        .. list::

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

            运行以下指令，烧录 eFuse：

            .. code:: bash

                espefuse --port PORT burn-efuse ENABLE_SECURITY_DOWNLOAD

Secure Boot v2 指南
~~~~~~~~~~~~~~~~~~~

* 建议将 Secure Boot 密钥存储在高度安全的地方，如可以使用物理或云 HSM 来存储 Secure Boot 私钥。请参阅 :ref:`remote-sign-v2-image` 获取更多详细信息。

.. only:: SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS

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

.. _enable-nvs-encryption-externally:

启用外部 NVS 加密
^^^^^^^^^^^^^^^^^

有关 NVS 加密及相关方案的详细信息，请参阅 :doc:`NVS 加密 </api-reference/storage/nvs_encryption>`。

.. only:: SOC_HMAC_SUPPORTED

    .. _enable-nvs-encryption-based-on-hmac:

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

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

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

        * HMAC 密钥 - 256 位的 HMAC 密钥，应存储在 eFuse 中。
        * NVS 加密密钥 - 用于加密 NVS 分区，在命令运行时通过 HMAC 密钥派生。

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

        .. code:: bash

            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 密钥

        使用以下命令在 {IDF_TARGET_NAME} 的 eFuse 中烧录 NVS 密钥：

        .. code:: bash

            espefuse --port PORT burn-key BLOCK hmac_key.bin HMAC_UP

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

    3. 生成加密的 NVS 分区

        主机上将会生成加密 NVS 分区。有关生成加密 NVS 分区的详细信息，请参阅 :ref:`generate-encrypted-nvs-partition`。为此，CSV 文件中应该包含 NVS 文件的全部内容。详情请参阅 :ref:`nvs-csv-file-format`。

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

        .. code:: bash

            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. 配置项目

        * 通过设置 :ref:`CONFIG_NVS_ENCRYPTION`，启用 `NVS 加密`。

        * 将 :ref:`CONFIG_NVS_SEC_KEY_PROTECTION_SCHEME` 设置为 ``CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC``，启用基于 HMAC 的 NVS 加密。

        * 通过设置 :ref:`CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID`，将 HMAC eFuse 密钥 ID 设为步骤 2 中烧录 eFuse 密钥的 ID。

    5. 烧录 NVS 分区

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

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

.. _enable-flash-enc-based-nvs-encryption:

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

在这种情况下，主机上生成 NVS 加密密钥，并将其烧录到芯片上，借助 :doc:`flash 加密 </security/flash-encryption>` 功能进行保护。

1. 生成 NVS 加密密钥

    使用 :doc:`NVS 分区生成工具 </api-reference/storage/nvs_partition_gen>`，可以生成相应的密钥。在主机上生成加密密钥，并将该密钥以加密状态存储在 {IDF_TARGET_NAME} 的 flash 中。

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

    .. code:: bash

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

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

2. 生成加密的 NVS 分区

    在主机上生成实际的加密 NVS 分区，详情请参阅 :ref:`generate-encrypted-nvs-partition`。为此，CSV 文件应包含 NVS 文件数据，详情请参阅 :ref:`nvs-csv-file-format`。

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

    .. code:: bash

        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. 配置项目

    * 通过启用 :ref:`CONFIG_NVS_ENCRYPTION` 来启用 `NVS 加密`。
    * 通过将 :ref:`CONFIG_NVS_SEC_KEY_PROTECTION_SCHEME` 设置为 ``CONFIG_NVS_SEC_KEY_PROTECT_USING_FLASH_ENC``，配置 NVS 使用基于 flash 加密的方案。

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

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

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