NVS 加密
概述
本文档主要介绍 NVS 加密功能,这一功能有助于实现设备在 flash 中的安全存储。
存储在 NVS 分区中的数据可以用 XTS-AES 进行加密,与磁盘加密标准 IEEE P1619 中提到的加密方式类似。加密时,每个条目都被视作一个 sector
,而条目的相对地址(相对于分区起始位置)作为 sector-number
输入加密算法。
NVS 加密:基于 flash 加密的方案
在这个方案中,NVS 加密所需的密钥存储在另一个分区中,该分区用 flash 加密 进行保护。因此,使用该方案时,必须先启用 flash 加密。
启用 flash 加密 时,默认启用 NVS 加密。这是因为 Wi-Fi 驱动程序会将凭据(如 SSID 和密码)储存在默认的 NVS 分区中。如已启用平台级加密,那么则需要同时默认启用 NVS 加密。
要用这一方案进行 NVS 加密,分区表中必须包含 NVS 密钥分区。在分区表选项 ( menuconfig
> Partition Table
) 中,有两个包含 NVS 密钥分区 的分区表,可通过项目配置菜单 ( idf.py menuconfig
) 进行选择。要了解如何配置和使用 NVS 加密功能,请参考示例 security/flash_encryption。
NVS 密钥分区
应用如果要使用 NVS 加密(使用基于 flash 加密的方案)编译时,须使用类型为 data
和子类型为 nvs_keys
的密钥分区。该分区应被标记为 encrypted
且最小为 4 KB (最小分区大小)。参考 分区表 了解详情。在分区表选项 ( menuconfig
> Partition Table
) 中,有两个包含 NVS 密钥分区 的额外分区表,可以直接用于 NVS 加密。分区的结构如下所示:
+-----------+--------------+-------------+----+
| XTS encryption key (32) |
+---------------------------------------------+
| XTS tweak key (32) |
+---------------------------------------------+
| CRC32 (4) |
+---------------------------------------------+
可以通过以下两种方式之一生成 NVS 密钥分区 中的 XTS 加密密钥:
在 ESP32 芯片上生成密钥
启用 NVS 加密时,可使用 API 函数
nvs_flash_init()
来初始化加密的默认 NVS 分区。该 API 函数会内部生成 ESP 芯片的 XTS 加密密钥,并找到第一个 NVS 密钥分区。然后,该 API 函数使用 nvs_flash/include/nvs_flash.h 提供的
nvs_flash_generate_keys()
API 函数,自动生成 NVS 密钥并存储到该分区。只有当相应的密钥分区为空时,才会生成和存储新密钥。然后,就可以利用nvs_flash_secure_init_partition()
用此密钥分区来读取安全配置,以初始化自定义的加密 NVS 分区。API 函数
nvs_flash_secure_init()
和nvs_flash_secure_init_partition()
不会内部生成密钥。如果要使用这两个 API 函数初始化加密的 NVS 分区,可以在启动后使用nvs_flash.h
提供的nvs_flash_generate_keys()
API 函数生成密钥,然后由该 API 函数将生成的密钥以加密形式写入密钥分区中。备注
请注意,在使用此方法启动应用程序前,必须完全擦除
nvs_keys
分区。否则,应用程序可能会生成ESP_ERR_NVS_CORRUPT_KEY_PART
错误代码,该代码假设nvs_keys
分区不为空并且包含格式错误的数据。可以使用以下命令来实现: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 # 如果启用了 Flash Encryption 或 Secure Boot,需要使用 "--esptool-erase-args=force" 来抑制错误: # "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
使用预生成的 NVS 密钥分区
如果 NVS 密钥分区 中的密钥不是由应用程序生成,则需要使用预先生成的密钥分区。可以使用 NVS 分区生成程序 生成包含 XTS 加密密钥的 NVS 密钥分区。然后使用以下两个命令将预生成的密钥分区存储到 flash 上:
1. 构建并烧写分区表
idf.py partition-table partition-table-flash2. 使用 parttool.py (参见 分区表 中分区工具相关章节)将密钥存储在 flash 上的 NVS 密钥分区 中
parttool.py --port PORT --partition-table-offset PARTITION_TABLE_OFFSET write_partition --partition-name="name of nvs_key partition" --input NVS_KEY_PARTITION_FILE # 如果启用了 Flash Encryption 或 Secure Boot,需要使用 "--esptool-erase-args=force" 来抑制错误: # "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备注
如果设备是在 flash 加密开发模式下加密的,那么要更新 NVS 密钥分区就需要使用 parttool.py 来加密 NVS 密钥分区,并提供一个指向你构建目录中未加密分区表的指针 (build/partition_table),因为设备上的分区表也是加密的。命令如下:
parttool.py --esptool-write-args encrypt --port PORT --partition-table-file=PARTITION_TABLE_FILE --partition-table-offset PARTITION_TABLE_OFFSET write_partition --partition-name="nvs_key 分区名称" --input NVS_KEY_PARTITION_FILE # 如果启用了 Flash Encryption 或 Secure Boot,需要使用 "--esptool-erase-args=force" 来抑制错误: # "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
由于密钥分区被标记为 encrypted
,且 flash 加密 已启用,引导加载程序会在首次启动时使用 flash 加密密钥对此分区进行加密。
一个应用程序可以使用不同的密钥对不同的 NVS 分区进行加密,从而拥有多个密钥分区。应用程序应为加密或解密操作提供正确的密钥分区和密钥信息。
加密读/写
NVS API 函数 nvs_get_*
或 nvs_set_*
也可用于读取和写入加密的 NVS 分区。
加密默认的 NVS 分区
要为默认 NVS 分区启用加密,无需额外的步骤。在启用 CONFIG_NVS_ENCRYPTION 时,API 函数
nvs_flash_init()
会根据使用的方案(由 CONFIG_NVS_SEC_KEY_PROTECTION_SCHEME 设置)在内部执行一些额外步骤,为默认的 NVS 分区启用加密。在基于 flash 加密的方案中,加密密钥由找到的第一个 NVS 密钥分区 生成。在 HMAC 方案中,密钥由 CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID 中烧录的 HMAC 密钥生成(参考 API 文档以了解更多详细信息)。
另外,还可使用 API 函数 nvs_flash_secure_init()
为默认 NVS 分区启用加密。
加密自定义 NVS 分区
要为一个自定义的 NVS 分区启用加密,使用 API 函数
nvs_flash_secure_init_partition()
代替nvs_flash_init_partition()
。使用 API 函数
nvs_flash_secure_init()
和nvs_flash_secure_init_partition()
时,为了在启用加密的情况下执行 NVS 读/写操作,应用程序应遵守以下步骤:填充 NVS 安全配置结构
nvs_sec_cfg_t
对基于 flash 加密的方案
使用 API 函数 esp_partition_find* 查找密钥分区和 NVS 数据分区。
使用 API 函数
nvs_flash_read_security_cfg()
或nvs_flash_generate_keys()
填充nvs_sec_cfg_t
结构体。
使用 API 函数
nvs_flash_secure_init()
或nvs_flash_secure_init_partition()
初始化 NVS flash 分区。使用 API 函数
nvs_open()
或nvs_open_from_partition()
打开一个命名空间。使用
nvs_get_*
或nvs_set_*
执行 NVS 读/写操作。使用
nvs_flash_deinit()
取消初始化 NVS 分区。
NVS Security Provider
组件 nvs_sec_provider 存储了 NVS 加密方案的所有特定实现代码,并且适用于未来的方案。此组件充当 nvs_flash 组件处理加密密钥的接口。组件 nvs_sec_provider 有自己的配置菜单,选定的安全方案和相应设置基于这一菜单注册到 nvs_flash 组件。
API 参考
Header File
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 onnvs_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.
- 参数
sec_scheme_cfg -- [in] Security scheme specific configuration data
sec_scheme_handle_out -- [out] Security scheme specific configuration handle
- 返回
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_deregister(nvs_sec_scheme_t *sec_scheme_handle)
Deregister the NVS encryption scheme registered with the given handle.
- 参数
sec_scheme_handle -- [in] Security scheme specific configuration handle
- 返回
ESP_OK, if the scheme registered with
sec_scheme_handle
was deregistered successfullyESP_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
-
const esp_partition_t *nvs_keys_part
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.