NVS Encryption

[中文]

Overview

This guide provides an overview of the NVS encryption feature. NVS encryption helps to achieve secure storage on the device flash memory.

Data stored in NVS partitions can be encrypted using XTS-AES in the manner similar to the one mentioned in disk encryption standard IEEE P1619. For the purpose of encryption, each entry is treated as one sector and relative address of the entry (w.r.t., partition-start) is fed to the encryption algorithm as sector-number.

NVS encryption can be facilitated by enabling CONFIG_NVS_ENCRYPTION and CONFIG_NVS_SEC_KEY_PROTECTION_SCHEME > CONFIG_NVS_SEC_KEY_PROTECT_USING_FLASH_ENC or CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC depending on the scheme to be used.

NVS Encryption: Flash Encryption-Based Scheme

In this scheme, the keys required for NVS encryption are stored in yet another partition, which is protected using Flash Encryption. Therefore, enabling Flash Encryption becomes a prerequisite for NVS encryption here.

NVS encryption should be enabled when Flash Encryption is enabled because the Wi-Fi driver stores credentials (like SSID and passphrase) in the default NVS partition. It is important to encrypt them if platform-level encryption is already enabled.

For using NVS encryption using this scheme, the partition table must contain the NVS Key Partition. Two partition tables containing the NVS Key Partition are provided for NVS encryption under the partition table option (menuconfig > Partition Table). They can be selected with the project configuration menu (idf.py menuconfig). Please refer to the example security/flash_encryption for how to configure and use the NVS encryption feature.

NVS Key Partition

An application requiring NVS encryption support (using the Flash Encryption-based scheme) needs to be compiled with a key-partition of the type data and subtype nvs_keys. This partition should be marked as encrypted and its size should be the minimum partition size (4 KB). Refer to Partition Tables for more details. Two additional partition tables which contain the NVS Key Partition are provided under the partition table option (menuconfig > Partition Table). They can be directly used for NVS encryption. The structure of these partitions is depicted below:

+-----------+--------------+-------------+----+
|              XTS encryption key (32)        |
+---------------------------------------------+
|              XTS tweak key (32)             |
+---------------------------------------------+
|                  CRC32 (4)                  |
+---------------------------------------------+

The XTS encryption keys in the NVS Key Partition can be generated in one of the following two ways.

Generate the keys on ESP32-C3 chip itself

  • When NVS encryption is enabled, the nvs_flash_init() API function can be used to initialize the encrypted default NVS partition. The API function internally generates the XTS encryption keys on the ESP chip. The API function finds the first NVS Key Partition.

  • Then the API function automatically generates and stores the NVS keys in that partition by making use of the nvs_flash_generate_keys() API function provided by nvs_flash/include/nvs_flash.h. New keys are generated and stored only when the respective key partition is empty. The same key partition can then be used to read the security configurations for initializing a custom encrypted NVS partition with help of nvs_flash_secure_init_partition().

  • The API functions nvs_flash_secure_init() and nvs_flash_secure_init_partition() do not generate the keys internally. When these API functions are used for initializing encrypted NVS partitions, the keys can be generated after startup using the nvs_flash_generate_keys() API function provided by nvs_flash.h. The API function then writes those keys onto the key-partition in encrypted form.

Note

Please note that nvs_keys partition must be completely erased before you start the application in this approach. Otherwise the application may generate the ESP_ERR_NVS_CORRUPT_KEY_PART error code assuming that nvs_keys partition is not empty and contains malformatted data. You can use the following command for this:

parttool.py --port PORT --partition-table-file=PARTITION_TABLE_FILE --partition-table-offset PARTITION_TABLE_OFFSET erase_partition --partition-type=data --partition-subtype=nvs_keys

# If Flash Encryption or Secure Boot are enabled then add "--esptool-erase-args=force" to suppress the error:
# "Active security features detected, erasing flash is disabled as a safety measure.  Use --force to override ..."
parttool.py --port PORT --esptool-erase-args=force --partition-table-file=PARTITION_TABLE_FILE --partition-table-offset PARTITION_TABLE_OFFSET erase_partition --partition-type=data --partition-subtype=nvs_keys

Use a pre-generated NVS key partition

This option will be required by the user when keys in the NVS Key Partition are not generated by the application. The NVS Key Partition containing the XTS encryption keys can be generated with the help of NVS Partition Generator Utility. Then the user can store the pre-generated key partition on the flash with help of the following two commands:

1. Build and flash the partition table

idf.py partition-table partition-table-flash

2. Store the keys in the NVS Key Partition (on the flash) with the help of parttool.py (see Partition Tool section in partition-tables for more details)

parttool.py --port PORT --partition-table-offset PARTITION_TABLE_OFFSET write_partition --partition-name="name of nvs_key partition" --input NVS_KEY_PARTITION_FILE

# If Flash Encryption or Secure Boot are enabled then add "--esptool-erase-args=force" to suppress the error:
# "Active security features detected, erasing flash is disabled as a safety measure.  Use --force to override ..."
parttool.py --port PORT --esptool-erase-args=force --partition-table-offset PARTITION_TABLE_OFFSET write_partition --partition-name="name of nvs_key partition" --input NVS_KEY_PARTITION_FILE

Note

If the device is encrypted in flash encryption development mode and you want to renew the NVS key partition, you need to tell parttool.py to encrypt the NVS key partition and you also need to give it a pointer to the unencrypted partition table in your build directory (build/partition_table) since the partition table on the device is encrypted, too. You can use the following command:

parttool.py --esptool-write-args encrypt --port PORT --partition-table-file=PARTITION_TABLE_FILE --partition-table-offset PARTITION_TABLE_OFFSET write_partition --partition-name="name of nvs_key partition" --input NVS_KEY_PARTITION_FILE

# If Flash Encryption or Secure Boot are enabled then add "--esptool-erase-args=force" to suppress the error:
# "Active security features detected, erasing flash is disabled as a safety measure.  Use --force to override ..."
parttool.py --esptool-erase-args=force --esptool-write-args encrypt --port PORT --partition-table-file=PARTITION_TABLE_FILE --partition-table-offset PARTITION_TABLE_OFFSET write_partition --partition-name="name of nvs_key partition" --input NVS_KEY_PARTITION_FILE

Since the key partition is marked as encrypted and Flash Encryption is enabled, the bootloader will encrypt this partition using flash encryption key on the first boot.

It is possible for an application to use different keys for different NVS partitions and thereby have multiple key-partitions. However, it is a responsibility of the application to provide the correct key-partition and keys for encryption or decryption.

NVS Encryption: HMAC Peripheral-Based Scheme

In this scheme, the XTS keys required for NVS encryption are derived from an HMAC key programmed in eFuse with the purpose esp_efuse_purpose_t::ESP_EFUSE_KEY_PURPOSE_HMAC_UP. Since the encryption keys are derived at runtime, they are not stored anywhere in the flash. Thus, this feature does not require a separate NVS Key Partition.

Note

This scheme enables us to achieve secure storage on ESP32-C3 without enabling flash encryption.

Important

Please take note that this scheme uses one eFuse block for storing the HMAC key required for deriving the encryption keys.

Note

The valid range for the config CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID is from 0 (hmac_key_id_t::HMAC_KEY0) to 5 (hmac_key_id_t::HMAC_KEY5). By default, the config is set to 6 (hmac_key_id_t::HMAC_KEY_MAX), which have to be configured before building the user application.

Note

Users can program their own HMAC key in eFuse block beforehand by using the following command:

idf.py -p PORT efuse-burn-key <BLOCK_KEYN> <hmac_key_file.bin> HMAC_UP

Encrypted Read/Write

The same NVS API functions nvs_get_* or nvs_set_* can be used for reading of, and writing to an encrypted NVS partition as well.

Encrypt the default NVS partition

  • To enable encryption for the default NVS partition, no additional step is necessary. When CONFIG_NVS_ENCRYPTION is enabled, the nvs_flash_init() API function internally performs some additional steps to enable encryption for the default NVS partition depending on the scheme being used (set by CONFIG_NVS_SEC_KEY_PROTECTION_SCHEME).

  • For the flash encryption-based scheme, the first NVS Key Partition found is used to generate the encryption keys while for the HMAC one, keys are generated using the HMAC key burnt in eFuse at CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID (refer to the API documentation for more details).

Alternatively, nvs_flash_secure_init() API function can also be used to enable encryption for the default NVS partition.

Encrypt a custom NVS partition

Note

While using the HMAC-based scheme, the above workflow can be used without enabling any of the config options for NVS encryption - CONFIG_NVS_ENCRYPTION, CONFIG_NVS_SEC_KEY_PROTECTION_SCHEME -> CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC and CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID to encrypt the default as well as custom NVS partitions with nvs_flash_secure_init() API.

NVS Security Provider

The component nvs_sec_provider stores all the implementation-specific code for the NVS encryption schemes and would also accommodate any future schemes. This component acts as an interface to the nvs_flash component for the handling of encryption keys. nvs_sec_provider has a configuration menu of its own, based on which the selected security scheme and the corresponding settings are registered for the nvs_flash component.

This component offers factory functions with which a particular security scheme can be registered without having to worry about the APIs to generate and read the encryption keys (e.g., nvs_sec_provider_register_hmac()). Refer to the security/nvs_encryption_hmac example for API usage.

API Reference

Header File

  • components/nvs_sec_provider/include/nvs_sec_provider.h

  • This header file can be included with:

    #include "nvs_sec_provider.h"
    
  • This header file is a part of the API provided by the nvs_sec_provider component. To declare that your component depends on nvs_sec_provider, add the following to your CMakeLists.txt:

    REQUIRES nvs_sec_provider
    

    or

    PRIV_REQUIRES nvs_sec_provider
    

Functions

esp_err_t nvs_sec_provider_register_flash_enc(const nvs_sec_config_flash_enc_t *sec_scheme_cfg, nvs_sec_scheme_t **sec_scheme_handle_out)

Register the Flash-Encryption based scheme for NVS Encryption.

Parameters
  • sec_scheme_cfg -- [in] Security scheme specific configuration data

  • sec_scheme_handle_out -- [out] Security scheme specific configuration handle

Returns

  • ESP_OK, if sec_scheme_handle_out was populated successfully with the scheme configuration;

  • ESP_ERR_INVALID_ARG, if scheme_cfg_hmac is NULL;

  • ESP_ERR_NO_MEM, No memory for the scheme-specific handle sec_scheme_handle_out

  • ESP_ERR_NOT_FOUND, if no nvs_keys partition is found

esp_err_t nvs_sec_provider_register_hmac(const nvs_sec_config_hmac_t *sec_scheme_cfg, nvs_sec_scheme_t **sec_scheme_handle_out)

Register the HMAC-based scheme for NVS Encryption.

Parameters
  • sec_scheme_cfg -- [in] Security scheme specific configuration data

  • sec_scheme_handle_out -- [out] Security scheme specific configuration handle

Returns

  • ESP_OK, if sec_scheme_handle_out was populated successfully with the scheme configuration;

  • ESP_ERR_INVALID_ARG, if scheme_cfg_hmac is NULL;

  • ESP_ERR_NO_MEM, No memory for the scheme-specific handle sec_scheme_handle_out

esp_err_t nvs_sec_provider_deregister(nvs_sec_scheme_t *sec_scheme_handle)

Deregister the NVS encryption scheme registered with the given handle.

Parameters

sec_scheme_handle -- [in] Security scheme specific configuration handle

Returns

  • ESP_OK, if the scheme registered with sec_scheme_handle was deregistered successfully

  • ESP_ERR_INVALID_ARG, if sec_scheme_handle is NULL;

Structures

struct nvs_sec_config_flash_enc_t

Flash encryption-based scheme specific configuration data.

Public Members

const esp_partition_t *nvs_keys_part

Partition of subtype nvs_keys holding the NVS encryption keys

struct nvs_sec_config_hmac_t

HMAC-based scheme specific configuration data.

Public Members

hmac_key_id_t hmac_key_id

HMAC Key ID used for generating the NVS encryption keys

Macros

ESP_ERR_NVS_SEC_BASE

Starting number of error codes

ESP_ERR_NVS_SEC_HMAC_KEY_NOT_FOUND

HMAC Key required to generate the NVS encryption keys not found

ESP_ERR_NVS_SEC_HMAC_KEY_BLK_ALREADY_USED

Provided eFuse block for HMAC key generation is already in use

ESP_ERR_NVS_SEC_HMAC_KEY_GENERATION_FAILED

Failed to generate/write the HMAC key to eFuse

ESP_ERR_NVS_SEC_HMAC_XTS_KEYS_DERIV_FAILED

Failed to derive the NVS encryption keys based on the HMAC-based scheme

NVS_SEC_PROVIDER_CFG_FLASH_ENC_DEFAULT()

Helper for populating the Flash encryption-based scheme specific configuration data.

NVS_SEC_PROVIDER_CFG_HMAC_DEFAULT()

Helper for populating the HMAC-based scheme specific configuration data.

Enumerations

enum nvs_sec_scheme_id_t

NVS Encryption Keys Protection Scheme.

Values:

enumerator NVS_SEC_SCHEME_FLASH_ENC

Protect NVS encryption keys using Flash Encryption

enumerator NVS_SEC_SCHEME_HMAC

Protect NVS encryption keys using HMAC peripheral

enumerator NVS_SEC_SCHEME_MAX

Was this page helpful?