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.
It is recommended to have an uninterrupted power supply while enabling security features on ESP32 SoCs. Power failures during the secure manufacturing process could cause issues that are hard to debug and, in some cases, may cause permanent boot-up failures.
This guide highlights an approach where security features are enabled with the assistance of an external host machine. Security workflows are broken down into various stages and key material is generated on the host machine; thus, allowing greater recovery chances in case of power or other failures. It also offers better timings for secure manufacturing, e.g., in the case of encryption of firmware on the host machine vs. on the device.
To enable the Secure Boot (SB) V2, it is necessary to keep the SB V2 key readable. To protect the key's readability, the write protection for ``RD_DIS`` (``ESP_EFUSE_WR_DIS_RD_DIS``) is applied. However, this action poses a challenge when attempting to enable Flash Encryption, as the Flash Encryption (FE) key needs to remain unreadable. This conflict arises because the ``RD_DIS`` is already write-protected, making it impossible to read protect the FE key.
In this case, all the eFuses related to Flash Encryption are written with help of the espefuse tool. More details about flash encryption can be found in the :doc:`Flash Encryption Guide </security/flash-encryption>`
At this point, the Flash Encryption must not be already enabled on the chip. Additionally, the flash on the chip needs to be erased, which can be done by running:
where ``BLOCK`` is a free keyblock between ``BLOCK_KEY0`` and ``BLOCK_KEY5``. And ``KEYPURPOSE`` is either ``XTS_AES_256_KEY_1``, ``XTS_AES_256_KEY_2``, ``XTS_AES_128_KEY``. See `{IDF_TARGET_NAME} Technical Reference Manual <{IDF_TARGET_TRM_EN_URL}>`_ for a description of the key purposes.
For AES-256 (512-bit key) - ``XTS_AES_256_KEY_1`` and ``XTS_AES_256_KEY_2``. ``espefuse.py`` supports burning both these two key purposes together with a 512-bit key to two separate key blocks via the virtual key purpose ``XTS_AES_256_KEY``. When this is used ``espefuse.py`` will burn the first 256 bits of the key to the specified ``BLOCK`` and burn the corresponding block key purpose to ``XTS_AES_256_KEY_1``. The last 256 bits of the key will be burned to the first free key block after ``BLOCK`` and the corresponding block key purpose to ``XTS_AES_256_KEY_2``
If you wish to specify exactly which two blocks are used then it is possible to divide the key into two 256-bit keys, and manually burn each half with ``XTS_AES_256_KEY_1`` and ``XTS_AES_256_KEY_2`` as key purposes:
For AES-128 key derived from 128 bits (SHA256(128 bits)) - ``XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS``. The FE key will be written in the lower part of eFuse BLOCK_KEY0. The upper 128 bits are not used and will remain available for reading by software. Using the special mode of the espefuse tool, shown in the ``For burning both keys together`` section below, the user can write their data to it using any espefuse commands.
If you only want to enable Flash Encryption in **Development** mode and want to keep the ability to disable it in the future, Update the {IDF_TARGET_CRYPT_CNT} value in the below command from {IDF_TARGET_CRYPT_CNT_MAX_VAL} to 0x1 (not recommended for production).
Please update the EFUSE_NAME with the eFuse that you need to burn. Multiple eFuses can be burned at the same time by appending them to the above command (e.g., EFUSE_NAME VAL EFUSE_NAME2 VAL2). More documentation about `espefuse.py` can be found `here <https://docs.espressif.com/projects/esptool/en/latest/esp32/espefuse/index.html>`_.
-:ref:`Enable Flash Encryption on boot <CONFIG_SECURE_FLASH_ENC_ENABLED>`
:esp32:- :ref:`Select Release mode <CONFIG_SECURE_FLASH_ENCRYPTION_MODE>` (Note that once Release mode is selected, the ``DISABLE_DL_ENCRYPT`` and ``DISABLE_DL_DECRYPT`` eFuse bits will be burned to disable Flash Encryption hardware in ROM Download Mode)
:esp32:- :ref:`Select UART ROM download mode (Permanently disabled (recommended)) <CONFIG_SECURE_UART_ROM_DL_MODE>` (Note that this option is only available when :ref:`CONFIG_ESP32_REV_MIN` is set to 3 (ESP32 V3).) The default choice is to keep UART ROM download mode enabled, however it is recommended to permanently disable this mode to reduce the options available to an attacker
:not esp32:- :ref:`Select Release mode <CONFIG_SECURE_FLASH_ENCRYPTION_MODE>` (Note that once Release mode is selected, the ``EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT`` eFuse bit will be burned to disable Flash Encryption hardware in ROM Download Mode)
:not esp32:- :ref:`Select UART ROM download mode (Permanently switch to Secure mode (recommended)) <CONFIG_SECURE_UART_ROM_DL_MODE>`. This is the default option and is recommended. It is also possible to change this configuration setting to permanently disable UART ROM download mode, if this mode is not needed
-:ref:`Select the appropriate bootloader log verbosity <CONFIG_BOOTLOADER_LOG_LEVEL>`
In the above command the offsets are used for a sample firmware, the actual offset for your firmware can be obtained by checking the partition table entry or by running `idf.py partition-table`. Please note that not all the binaries need to be encrypted, the encryption applies only to those generated from the partitions which are marked as ``encrypted`` in the partition table definition file. Other binaries are flashed unencrypted, i.e., as a plain output of the build process.
The above files can then be flashed to their respective offset using ``esptool.py``. To see all of the command line options recommended for ``esptool.py``, see the output printed when ``idf.py build`` succeeds.
When the application contains the following partition: ``otadata``, ``nvs_encryption_keys`` they need to be encrypted as well. Please refer to :ref:`encrypted-partitions` for more details about encrypted partitions.
If the flashed ciphertext file is not recognized by the {IDF_TARGET_NAME} when it boots, check that the keys match and that the command line arguments match exactly, including the correct offset. It is important to provide the correct offset as the ciphertext changes when the offset changes.
If your ESP32 uses non-default :ref:`FLASH_CRYPT_CONFIG value in eFuse <setting-flash-crypt-config>` then you will need to pass the ``--flash_crypt_conf`` argument to ``espsecure.py`` to set the matching value. This will not happen when the Flash Encryption is enabled by the firmware bootloader but may happen when burning eFuses manually to enable flash encryption.
The command ``espsecure.py decrypt_flash_data`` can be used with the same options (and different input/output files), to decrypt ciphertext flash contents or a previously encrypted file.
Once the Flash Encryption has been enabled for the device, the key **must be deleted immediately**. This ensures that the host cannot produce encrypted binaries for the same device going forward. This step is important to reduce the vulnerability of the flash encryption key.
In this workflow, we shall use ``espsecure`` tool to generate signing keys and use the ``espefuse`` tool to burn the relevant eFuses. The details about the Secure Boot V2 process can be found at :doc:`Secure Boot V2 Guide </security/secure-boot-v2>`
A total of 3 keys can be used for Secure Boot V2 at once. These should be computed independently and stored separately. The same command with different key file names can be used to generate multiple Secure Boot V2 signing keys. It is recommended to use multiple keys in order to reduce dependency on a single key.
In case of multiple digests, the other digests can be burned sequentially by changing the key purpose to ``SECURE_BOOT_DIGEST1`` and ``SECURE_BOOT_DIGEST2`` respectively.
:SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS:- ``SECURE_BOOT_AGGRESSIVE_REVOKE``: Aggressive revocation of key digests, see :ref:`secure-boot-v2-aggressive-key-revocation` for more details.
The respective eFuses can be burned by running:
..code:: bash
espefuse.py burn_efuse --port PORT EFUSE_NAME 0x1
..note::
Please update the EFUSE_NAME with the eFuse that you need to burn. Multiple eFuses can be burned at the same time by appending them to the above command (e.g., EFUSE_NAME VAL EFUSE_NAME2 VAL2). More documentation about `espefuse.py` can be found `here <https://docs.espressif.com/projects/esptool/en/latest/esp32/espefuse/index.html>`_
B) Secure Boot V2-related eFuses
i) Disable the read-protection option:
The Secure Boot digest burned in the eFuse must be kept readable otherwise the Secure Boot operation would result in a failure. To prevent the accidental enabling of read protection for this key block, the following eFuse needs to be burned:
..important::
After burning above-mentioned eFuse, the read protection cannot be enabled for any key. E.g., if Flash Encryption which requires read protection for its key is not enabled at this point, then it cannot be enabled afterwards. Please ensure that no eFuse keys are going to need read protection after completing this step.
The unused digest slots need to be revoked when we are burning the Secure Boot key. The respective slots can be revoked by running
..code:: bash
espefuse.py --port PORT --chip {IDF_TARGET_PATH_NAME} burn_efuse EFUSE_REVOKE_BIT
The ``EFUSE_REVOKE_BIT`` in the above command can be ``SECURE_BOOT_KEY_REVOKE0`` or ``SECURE_BOOT_KEY_REVOKE1`` or ``SECURE_BOOT_KEY_REVOKE2``. Please note that only the unused key digests must be revoked. Once revoked, the respective digest cannot be used again.
By default, the ROM bootloader would only verify the :ref:`second-stage-bootloader` (firmware bootloader). The firmware bootloader would verify the app partition only when the :ref:`CONFIG_SECURE_BOOT` option is enabled (and :ref:`CONFIG_SECURE_BOOT_VERSION` is set to ``SECURE_BOOT_V2_ENABLED``) while building the bootloader.
For ESP32, Secure Boot V2 is available only for ESP32 ECO3 onwards. To view the "Secure Boot V2" option the chip revision should be changed to revision v3.0 (ECO3). To change the chip revision, set "Minimum Supported ESP32 Revision" to "Rev 3.0 (ECO3)" in "Component Config" -> "Hardware Settings" -> "Chip Revision".
b) Disable the option :ref:`CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES` for the project in the :ref:`project-configuration-menu`. This shall make sure that all the generated binaries are secure padded and unsigned. This step is done to avoid generating signed binaries as we are going to manually sign the binaries using ``espsecure`` tool.
The Secure Boot V2 workflow only verifies the ``bootloader`` and ``application`` binaries, hence only those binaries need to be signed. The other binaries (e.g., ``partition-table.bin``) can be flashed as they are generated in the build stage.
If multiple keys Secure Boot keys are to be used then the same signed binary can be appended with a signature block signed with the new key as follows:
The above files along with other binaries (e.g., partition table) can then be flashed to their respective offset using ``esptool.py``. To see all of the command line options recommended for ``esptool.py``, see the output printed when ``idf.py build`` succeeds. The flash offset for your firmware can be obtained by checking the partition table entry or by running ``idf.py partition-table``.
espefuse.py --port PORT burn_efuse UART_DOWNLOAD_DIS
..only:: not esp32
Enable Security Download mode:
..list::
-``ENABLE_SECURITY_DOWNLOAD``: Enable Secure ROM download mode
The eFuse can be burned by running:
..code:: bash
espefuse.py --port PORT burn_efuse ENABLE_SECURITY_DOWNLOAD
Secure Boot V2 Guidelines
~~~~~~~~~~~~~~~~~~~~~~~~~
* It is recommended to store the Secure Boot key in a highly secure place. A physical or a cloud HSM may be used for secure storage of the Secure Boot private key. Please take a look at :ref:`remote-sign-v2-image` for more details.
..only:: SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS
* It is recommended to use all the available digest slots to reduce dependency on a single private key.
.._enable-nvs-encryption-externally:
Enable NVS Encryption Externally
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The details about NVS Encryption and related schemes can be found at :doc:`NVS Encryption </api-reference/storage/nvs_encryption>`.
..only:: SOC_HMAC_SUPPORTED
.._enable-nvs-encryption-based-on-hmac:
Enable NVS Encryption based on HMAC
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Generate the HMAC key and NVS Encryption key
In the HMAC based NVS scheme, there are two keys:
* HMAC key - this is a 256 bit HMAC key that shall be stored in the eFuse
* NVS Encryption key - This is the NVS Encryption key that is used to encrypt the NVS partition. This key is derived at run-time using the HMAC key.
The above keys can be generated with the :component_file:`nvs_flash/nvs_partition_generator/nvs_partition_gen.py` script with help of the following command:
We shall generate the actual encrypted NVS partition on host. More details about generating the encryption NVS partition can be found at :ref:`generate-encrypted-nvs-partition`.
For this purpose, the contents of the NVS file shall be available in a CSV file. Please check out :ref:`nvs-csv-file-format` for more details.
* CSV file name - In this case `sample_singlepage_blob.csv` is the CSV file which contains the NVS data, Replace this with the file you wish to choose.
* NVS partition offset - This is the offset at which that NVS partition shall be stored in the flash of {IDF_TARGET_NAME}. The offset of your nvs-partition can be found be executing `idf.py partition-table` in the projtect directory. Please update the sample value of `0x3000` in the above-provided command to the correct offset.
The NVS partition (``nvs_encr_partition.bin``) generated in Step 3 can then be flashed to its respective offset using ``esptool.py``. To see all of the command line options recommended for ``esptool.py``, check the output printed when ``idf.py build`` succeeds.
If Flash encryption is enabled for the chip then please encrypt the partition first before flashing. You may refer the flashing related steps of `Flash Encryption workflow <enable-flash-encryption-externally_>`_.
In this case we generate NVS Encryption keys on a host. This key is then flashed on the chip and protected with help of the :doc:`Flash Encryption </security/flash-encryption>` feature.
For generation of respective keys, we shall use :doc:`NVS partition generator utility </api-reference/storage/nvs_partition_gen>`. We shall generate the encryption key on host and this key key shall be stored on the flash of {IDF_TARGET_NAME} in encrypted state.
We shall generate the actual encrypted NVS partition on host. More details about generating the encryption NVS partition can be found at :ref:`generate-encrypted-nvs-partition`.
For this, the contents of the NVS file shall be available in a CSV file. Please refer :ref:`nvs-csv-file-format` for more details.
* CSV file name - In this case `sample_singlepage_blob.csv` is the CSV file which contains the NVS data, Replace this with the file you wish to choose.
* NVS partition offset - This is the offset at which that NVS partition shall be stored in the flash of {IDF_TARGET_NAME}. The offset of your nvs-partition can be found be executing `idf.py partition-table` in the projtect directory. Please update the sample value of `0x3000` in the above-provided command to the correct offset.
* Enable `NVS Encryption` by enabling :ref:`CONFIG_NVS_ENCRYPTION`.
* Set NVS to use Flash Encryption based scheme by setting :ref:`CONFIG_NVS_SEC_KEY_PROTECTION_SCHEME` to ``CONFIG_NVS_SEC_KEY_PROTECT_USING_FLASH_ENC``.
The NVS partition (``nvs_encr_partition.bin``) and NVS Encryption key (``nvs_encr_key.bin``) can then be flashed to their respective offset using ``esptool.py``. To see all of the command line options recommended for ``esptool.py``, check the output printed when ``idf.py build`` succeeds.
If Flash encryption is enabled for the chip then please encrypt the partition first before flashing. You may refer the flashing related steps of `Flash Encryption workflow <enable-flash-encryption-externally_>`_.