ESP-IDF NVS 分区生成工具 nvs_partition_gen.py 详解:从 CSV 键值对生成、加密与解密 NVS 闪存分区
【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf
在 ESP-IDF(Espressif IoT Development Framework)中,Non-Volatile Storage(NVS,非易失性存储)是跨重启持久化配置数据的核心组件。但在 ODM/OEM 量产场景中,厂商往往需要在产线上为每台设备烧录不同的序列号、校准参数或证书等数据,而不能依赖设备开机后的运行时写入。本文介绍的 NVS 分区生成程序(NVS Partition Generator)正是解决这一问题的工具:它根据 CSV 文件中的键值对直接生成与 nvs_flash 组件 结构兼容的二进制分区文件,支持多页 blob、XTS-AES 加密以及基于 HMAC 的密钥保护方案。读完本文,你将掌握 CSV 输入文件的完整格式规范、四个子命令(generate / generate-key / encrypt / decrypt)的全部参数,并理解生成分区内部的条目结构与 CMake 集成方式。
工具定位与工作原理
NVS 分区生成程序的入口脚本位于 nvs_partition_gen.py。从源码看,该脚本本身只有一行核心逻辑:
if __name__ == '__main__': sys.exit(subprocess.run([sys.executable, '-m', 'esp_idf_nvs_partition_gen'] + sys.argv[1:]).returncode)它是一个薄封装层,真正的实现由 pip 包esp-idf-nvs-partition-gen承担(该包在 tools/requirements/requirements.core.txt 中声明),执行时通过python -m以模块方式调用。因此只要 IDF 工具链的 Python 依赖已安装,直接运行脚本即可。
该程序的典型应用场景是设备生产时的外部烧录数据:制造商使用同一份应用固件,通过自定义参数(如序列号)为每台设备生成内容不同的 NVS 二进制分区,再用烧录工具将其单独写入分区表中的 NVS 分区。相比运行时调用nvs_set_str()等 API,这种方式让设备在出厂前即持有完整配置,避免首次启动的初始化窗口。
准备工作
在加密模式下使用该程序,需要额外安装cryptographyPython 包(XTS-AES 加解密依赖它)。仓库根目录下的 Python 依赖清单(当前版本位于tools/requirements/目录)包含工具运行所需的包,建议先完整安装。
CSV 文件格式
输入 CSV 文件每行包含四个以逗号分隔的参数,具体含义如下表:
| 序号 | 参数 | 描述 | 说明 |
|---|---|---|---|
| 1 | Key | 主键,应用程序可通过查询此键来获取数据 | 长度不超过 15 个字符(含结尾的 NULL,见下文结构分析) |
| 2 | Type | 支持file、data和namespace | 与 NVS API 中的条目类型一致 |
| 3 | Encoding | 决定二进制文件中 value 被编码成的类型,支持u8、i8、u16、i16、u32、i32、u64、i64、string、hex2bin、base64和binary | string与binary的区别在于:string数据以 NULL 字符结尾,binary数据则不是。file类型当前仅支持hex2bin、base64、string和binary编码 |
| 4 | Value | 数据值 | namespace条目的encoding和value应为空(固定值,单元格内容会被忽略) |
需要注意的格式约束:
- CSV 文件的第一行应始终为列标题
key,type,encoding,value,不可省略; - 逗号
,前后不能有空格,每行末尾也不能有多余空格; - 字符串值中若包含逗号或换行,需用双引号包裹(仓库中的示例文件即采用了多行字符串写法)。
此类 CSV 文件的结构示例如下:
key,type,encoding,value <-- 列标题 namespace_name,namespace,, <-- 第一个条目为 "namespace" key1,data,u8,1 key2,file,string,/path/to/file仓库内提供了三个可直接参考的示例文件(位于 nvs_partition_generator 目录):
- sample_singlepage_blob.csv:覆盖 u8/i8/u16/u32/i32 数值型键、string、hex2bin、base64,以及
file类型下分别以 hex、base64、string、binary 编码引用 testdata 目录中文件的完整示例; - sample_multipage_blob.csv:与上者类似,但引用的是可跨多页的大 blob(testdata/sample_multipage_blob.bin);
- sample_val.csv:一个命名空间
storage下的最简键值集合。
file类型条目的 Value 是文件路径而非内联数据,例如hexFileKey,file,hex2bin,testdata/sample.hex——生成器会读取该文件、按编码解析后写入分区。
NVS 条目与命名空间的关联规则
CSV 文件中的条目与 NVS 命名空间(namespace)的对应关系遵循顺序生效规则:
- CSV 文件中第一个条目应始终为
namespace类型,声明初始命名空间; - 如 CSV 文件中出现命名空间条目,后续所有条目均被视为该命名空间的一部分,直至遇到下一个命名空间条目;
- 找到新命名空间条目后,后续所有条目都会归属到新的命名空间。
这意味着命名空间不是每行的属性,而是"作用域"概念:
key,type,encoding,value storage,namespace,, u8_key,data,u8,255 # 归属 storage 命名空间 wifi,namespace,, ssid,data,string,"my-wifi" # 归属 wifi 命名空间分区内部结构:条目如何编码
生成器输出的二进制分区由 4096 字节(0x1000)的页组成。从仓库中用于校验生成结果的测试代码 test_nvs_gen_check.py 的create_entry_data_bytearray函数,可以印证单条目的二进制布局(每个条目 32 字节):
| 偏移 | 长度 | 内容 |
|---|---|---|
| 0 | 1 字节 | 命名空间索引(namespace_index) |
| 1 | 1 字节 | 条目类型(data/string/file 等) |
| 2 | 1 字节 | 跨度(span,blob 占用的连续条目数) |
| 3 | 1 字节 | blob 数据块索引(chunk_index) |
| 4–7 | 4 字节 | CRC32(初值0xFFFFFFFF,覆盖前 4 字节 + 键 + 值) |
| 8–23 | 16 字节 | 键,NULL 字节填充至 16 字节且末位强制为 0 |
| 24–31 | 8 字节 | 值数据(定长整数按小端序存储;字符串/文件条目为指针信息) |
这解释了 CSV 格式中的两条隐性约束:键被 NULL 填充到 16 字节,所以键名实际最长 15 个字符;定长数值只取低 8 字节写入,与编码选择(u8/i8/u16/i16/u32/i32)共同决定有效位宽。string与binary的差别则体现在值末尾是否追加 NULL 终止符上。
支持多页 blob
默认情况下(版本 2),二进制 blob 可以跨多个 4096 字节的页存储,通过条目中的span(占用的条目数)和chunk_index(数据块索引)串联。版本 1 对应旧版格式,禁用多页 blob,单个 blob 只能落在单页内——仓库中保留了旧格式参考文件 part_old_blob_format.bin 供比对。
两个版本的生成命令:
# 版本 1:禁用多页 blob python nvs_partition_gen.py generate sample_singlepage_blob.csv sample.bin 0x3000 --version 1 # 版本 2:启用多页 blob(默认) python nvs_partition_gen.py generate sample_multipage_blob.csv sample.bin 0x4000 --version 2程序命令总览
总用法::
python nvs_partition_gen.py [-h] {generate,generate-key,encrypt,decrypt} ...| 参数 | 描述 |
|---|---|
-h/--help | 显示帮助信息并退出 |
四个子命令:
| 参数 | 描述 |
|---|---|
generate | 生成(明文)NVS 分区 |
generate-key | 生成加密密钥(分区) |
encrypt | 加密 NVS 分区 |
decrypt | 解密 NVS 分区 |
运行python nvs_partition_gen.py {command} -h可查看各子命令的完整帮助。
子命令一:generate(生成明文 NVS 分区,默认模式)
python nvs_partition_gen.py generate [-h] [--version {1,2}] [--outdir OUTDIR] input output size位置参数:
| 参数 | 描述 |
|---|---|
input | 待解析的 CSV 文件路径 |
output | NVS 二进制文件的输出路径 |
size | NVS 分区大小(字节为单位,且为 4096 的整数倍) |
可选参数:
| 参数 | 描述 |
|---|---|
-h/--help | 显示帮助信息并退出 |
--version {1,2} | 设置多页 blob 版本,默认为版本 2。版本 1:禁用多页 blob;版本 2:启用多页 blob |
--outdir OUTDIR | 输出目录,用于存储创建的文件(默认当前目录) |
基本运行命令:
python nvs_partition_gen.py generate sample_singlepage_blob.csv sample.bin 0x3000NVS 分区最小尺寸为 0x3000 字节。将生成的二进制文件烧录至设备时,请确保分区表中的 NVS 分区大小与应用的 sdkconfig 设置一致(例如CONFIG_NVS_FLASH_ENCRYPTION_ENABLED等选项要与分区是否加密匹配)。
子命令二:generate-key(生成加密密钥)
支持 HMAC 外设的 SoC 上的用法::
python nvs_partition_gen.py generate-key [-h] [--key_protect_hmac] [--kp_hmac_keygen] [--kp_hmac_keyfile KP_HMAC_KEYFILE] [--kp_hmac_inputkey KP_HMAC_INPUTKEY] [--keyfile KEYFILE] [--outdir OUTDIR]不支持 HMAC 外设的 SoC 上的用法::
python nvs_partition_gen.py generate-key [-h] [--keyfile KEYFILE] [--outdir OUTDIR]通用可选参数:
| 参数 | 描述 |
|---|---|
-h/--help | 显示帮助信息并退出 |
--keyfile KEYFILE | 加密密钥分区文件的输出路径 |
--outdir OUTDIR | 输出目录(默认当前目录) |
仅适用于 HMAC 方案的可选参数(SOC_HMAC_SUPPORTED):
| 参数 | 描述 |
|---|---|
--key_protect_hmac | 设置后使用基于 HMAC 的 NVS 加密密钥保护方案,否则使用基于 flash 加密的默认方案 |
--kp_hmac_keygen | 为基于 HMAC 的加密方案生成 HMAC 密钥 |
--kp_hmac_keyfile KP_HMAC_KEYFILE | HMAC 密钥文件的输出路径 |
--kp_hmac_inputkey KP_HMAC_INPUTKEY | 包含 HMAC 密钥的文件,用于生成 NVS 加密密钥 |
运行命令:
# 仅生成(flash 加密方案下的)加密密钥分区 python nvs_partition_gen.py generate-key # 为基于 HMAC 的方案同时生成 HMAC 密钥和 NVS 加密密钥 python nvs_partition_gen.py generate-key --key_protect_hmac --kp_hmac_keygen # 基于用户已有的 HMAC 密钥生成 NVS 加密密钥 python nvs_partition_gen.py generate-key --key_protect_hmac --kp_hmac_inputkey testdata/sample_hmac_key.bin说明:
--key_protect_hmac --kp_hmac_keygen会生成<outdir>/keys/keys-<timestamp>.bin格式的加密密钥和<outdir>/keys/hmac-keys-<timestamp>.bin格式的 HMAC 密钥;- 可将自定义文件名作为参数提供给 HMAC 密钥和加密密钥(通过
--keyfile/--kp_hmac_keyfile)。
加密方案的整体原理(XTS-AES-128、密钥分区结构、flash 加密与 HMAC 两种密钥保护方式的区别)详见 NVS 加密指南。
子命令三:encrypt(生成 NVS 加密分区)
支持 HMAC 外设的 SoC 上的用法::
python nvs_partition_gen.py encrypt [-h] [--version {1,2}] [--keygen] [--keyfile KEYFILE] [--inputkey INPUTKEY] [--outdir OUTDIR] [--key_protect_hmac] [--kp_hmac_keygen] [--kp_hmac_keyfile KP_HMAC_KEYFILE] [--kp_hmac_inputkey KP_HMAC_INPUTKEY] input output size不支持 HMAC 外设的 SoC 上的用法(去掉 HMAC 相关参数)::
python nvs_partition_gen.py encrypt [-h] [--version {1,2}] [--keygen] [--keyfile KEYFILE] [--inputkey INPUTKEY] [--outdir OUTDIR] input output size位置参数与generate相同:input(CSV 路径)、output(NVS 二进制输出路径)、size(分区大小,4096 的整数倍)。
通用可选参数:
| 参数 | 描述 |
|---|---|
-h/--help | 显示帮助信息并退出 |
--version {1,2} | 多页 blob 版本设置,默认版本 2 |
--keygen | 生成 NVS 分区加密密钥 |
--keyfile KEYFILE | 密钥文件的输出路径 |
--inputkey INPUTKEY | 内含 NVS 分区加密密钥的文件 |
--outdir OUTDIR | 输出目录(默认当前目录) |
HMAC 方案专属参数与generate-key中的--key_protect_hmac/--kp_hmac_keygen/--kp_hmac_keyfile/--kp_hmac_inputkey含义相同。
典型用法:
# 1. 由生成程序生成加密密钥,同时加密分区 python nvs_partition_gen.py encrypt sample_singlepage_blob.csv sample_encr.bin 0x3000 --keygen # 创建的加密密钥格式为 <outdir>/keys/keys-<timestamp>.bin # 2. HMAC 方案:同时生成加密密钥和 HMAC 密钥 python nvs_partition_gen.py encrypt sample_singlepage_blob.csv sample_encr.bin 0x3000 \ --keygen --key_protect_hmac --kp_hmac_keygen # 3. HMAC 方案:使用用户提供的 HMAC 密钥派生加密密钥 python nvs_partition_gen.py encrypt sample_singlepage_blob.csv sample_encr.bin 0x3000 \ --keygen --key_protect_hmac --kp_hmac_inputkey testdata/sample_hmac_key.bin # 4. 生成密钥并存储到自定义文件 python nvs_partition_gen.py encrypt sample_singlepage_blob.csv sample_encr.bin 0x3000 \ --keygen --keyfile sample_keys.bin # 此时密钥位于 <outdir>/keys/sample_keys.bin # 5. 将已有的加密密钥文件作为二进制输入进行加密 python nvs_partition_gen.py encrypt sample_singlepage_blob.csv sample_encr.bin 0x3000 \ --inputkey sample_keys.bin注意:加密密钥存储在新建文件的keys/目录下,与 NVS 密钥分区结构兼容,即该密钥文件本身可作为独立的密钥分区烧录。密钥分区结构的详细说明见 NVS 加密指南 中的nvs_encr_key_partition章节。仓库测试数据中也提供了示例密钥文件 testdata/sample_encryption_keys.bin 与 testdata/sample_hmac_key.bin 可供解密验证时参考。
子命令四:decrypt(解密 NVS 分区)
python nvs_partition_gen.py decrypt [-h] [--outdir OUTDIR] input key output位置参数:
| 参数 | 描述 |
|---|---|
input | 待解析的 NVS 加密分区文件路径 |
key | 含有解密密钥的文件路径 |
output | 已解密的二进制文件输出路径 |
可选参数:
| 参数 | 描述 |
|---|---|
-h/--help | 显示帮助信息并退出 |
--outdir OUTDIR | 输出目录(默认当前目录) |
解密命令示例:
python nvs_partition_gen.py decrypt sample_encr.bin sample_keys.bin sample_decr.bin与构建系统集成:CMake 方式生成分区
除了手动调用nvs_partition_gen.py,也可以直接在组件的 CMakeLists.txt 中通过 CMake 函数生成 NVS 分区镜像,其底层封装了同一生成器(函数定义见 project_include.cmake):
nvs_create_partition_image(<partition> <csv> [FLASH_IN_PROJECT] [DEPENDS dep dep dep ...])| 参数 | 描述 |
|---|---|
partition | NVS 分区名(分区表中的名称) |
csv | 待解析的 CSV 文件路径 |
FLASH_IN_PROJECT(可选) | 指定后将镜像纳入项目烧录清单,idf.py flash时自动烧录 |
DEPENDS(可选) | 声明该命令依赖的文件,触发重新生成 |
若不指定FLASH_IN_PROJECT,镜像仍会生成,但需用idf.py <partition>-flash手动烧录(例如分区名为nvs时执行idf.py nvs-flash)。该函数必须从组件的CMakeLists.txt中调用,且目前仅支持非加密分区;加密分区仍需手动执行encrypt子命令。
生成结果的完整性校验
仓库配套提供了 NVS 分区检查工具与自动化测试 test_nvs_gen_check.py,它直接以模块方式导入esp_idf_nvs_partition_gen生成器,将内存中的生成结果交给nvs_parser/nvs_check做结构化校验。从测试用例可以看到校验维度包括:
- 分区大小检查(
check_partition_size)与空页存在性检查(check_empty_page_present)——NVS 正常运行要求分区内保留至少一个空页; - 页 CRC 与空页内容检查(
check_page_crc/check_empty_page_content)——每个非空页的页头 CRC 必须校验通过; - 重复键检测(
identify_entry_duplicates)——验证同一命名空间下不应出现重复键,测试setup_bad_same_key_*系列用例精确统计了重复条目数(如test_check_duplicates_bad_same_key_different_pages断言恰好发现 9 组重复键); - 最小化 JSON 输出(
print_minimal_json)——测试test_print_minimal_json断言输出为合法 JSON,包含namespace、key、encoding、data、state、is_empty字段,且 blob 数据可经 base64 解码后与原始 sample_multipage_blob.bin 逐字节一致; - 非 ASCII 字符串的 CRC 一致性校验(
test_check_non_ascii_string)。
这说明生成器输出的分区不是"黑盒二进制",而是可以被解析、校验和审计的——生产流程中可用同样的解析逻辑验证烧录前的分区镜像。
使用注意事项
原文档明确列出的三条重要限制,在实际使用中务必注意:
- 不检查重复键:分区生成程序不会对重复键进行检查,而是将数据同时写入这两个重复键中。请注意不要使用同名的键(这也是上文重复键检测测试所针对的问题;从测试代码看,生成器新版本对跨页重复命名空间条目只切换当前命名空间索引而不重复写入,但同名数据键仍会重复落盘)。
- 字段顺序影响空间利用率:新页面创建后,前一页的空白处不会再写入数据。CSV 文件中的字段须按次序排列以优化内存——将小条目(数值、短字符串)排在大 blob 之前,能显著减少跨页碎片。
- 暂不支持 64 位数据类型:虽然 CSV 的 encoding 列列出了
u64/i64,但当前生成器尚不支持这两种 64 位数据类型的实际写入。
其他实践要点:
- 分区大小参数(
size)必须以 4096 为整数倍,最小 0x3000; - 生成二进制文件烧录至设备时,确保与应用 sdkconfig 中 NVS 相关配置(分区大小、是否启用加密、HMAC 方案)一致,否则
nvs_flash_init()会初始化失败; - 加密模式需安装
cryptography包;HMAC 相关参数(--key_protect_hmac系列)仅在支持 HMAC 外设的芯片上可用。
小结
NVS 分区生成程序将"CSV 键值描述 → 可烧录 NVS 分区镜像"这一量产环节工具化:generate处理明文分区,generate-key/encrypt/decrypt覆盖 XTS-AES 加密分区及其密钥管理(含 flash 加密与 HMAC 两种密钥保护方案),--version {1,2}控制多页 blob 格式,配合 CMake 的nvs_create_partition_image函数可无缝嵌入构建流程。结合仓库中的示例 CSV、测试数据与nvs_check校验测试,开发者可以完整复现并验证从产线烧录到设备端读取的整条数据链路。
【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考