
ESP-IDF HMAC 硬件加速器完全指南eFuse 密钥、PSA Opaque 驱动与 JTAG 安全使能【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf摘要导读本文以 ESP-IDF 官方外设文档《Hash-Based Message Authentication Code (HMAC)》为核心系统讲解基于哈希的消息认证码HMAC在乐鑫 SoC 上的硬件加速实现。你将掌握如何把密钥烧写进 eFuse 物理块并设定 Key Purpose、Upstream/Downstream 三种应用场景软件认证、RSA 数字签名、JTAG 使能、如何通过 PSA Crypto Opaque 驱动用psa_mac_compute一次性计算 SHA256-HMAC以及软禁用 JTAG 后的完整恢复流程。读完即可在真实芯片上复现本文所有代码与命令。HMAC 是什么用预共享密钥验证消息的完整性与真实性Hash-based Message Authentication Code基于哈希的消息认证码HMAC是一种安全的认证技术通信双方使用一个预共享密钥pre-shared key对消息计算带密钥的哈希摘要从而验证消息的真实性与完整性。ESP-IDF 将该模块以硬件加速方式实现密钥直接烧写在 eFuse 物理块中SHA256-HMAC 的生成由专用外设完成。在 ESP-IDF 中该功能的核心官方文档位于 docs/en/api-reference/peripherals/hmac.rst本文即以该文档为骨架结合仓库源码与示例深入展开。通用认证方案Generalized Application Scheme设想通信双方 A 与 B 需要验证彼此发送消息的真实性与完整性。在开始通信之前双方必须先通过安全信道交换共享密钥。之后B 验证 A 的消息可按下述流程进行A 对要发送的消息计算 HMACA 将消息与 HMAC 一并发送给 BB 自己重新计算收到消息的 HMACB 比对收到的 HMAC 与本地计算出的 HMAC 是否一致。若两者匹配则消息是真实可信的。不过 HMAC 并不局限于这一种用法它同样可以用于支持 HMAC 的挑战-响应协议challenge-response protocol或作为后续安全模块的密钥输入详见下文 Downstream 模式。HMAC on ESP32eFuse 中的密钥与 Upstream/Downstream 模式在乐鑫 SoC 上HMAC 模块使用的秘密密钥被烧写进eFuse中并且可以做到对密码学模块之外的任何资源完全不可访问从而避免密钥泄露。从硬件工作流的角度看HMAC 外设可以将计算结果输出给软件也可以直接交付给其他外设。这一点在 HAL 层的枚举 hal/hmac_types.h 中体现得很清楚typedef enum { HMAC_OUTPUT_USER 0, /** Let user provide a message and read the HMAC result */ HMAC_OUTPUT_DS 1, /** HMAC is provided to the DS peripheral to decrypt DS private key parameters */ HMAC_OUTPUT_JTAG_ENABLE 2, /** HMAC is used to enable JTAG after soft-disabling it */ HMAC_OUTPUT_ALL 3 /** HMAC is used for both as DS input for or enabling JTAG */ } hmac_hal_output_t;根据芯片是否支持 RSA 数字签名外设SOC_DIG_SIGN_SUPPORTEDHMAC 模块对应两种/三种应用场景HMAC 为软件使用而生成Upstream 模式HMAC 作为 RSA 数字签名外设RSA_DS的密钥Downstream 模式仅支持 RSA_DS 的芯片HMAC 用于使能被软禁用的 JTAG 接口Downstream 模式。第一种模式称为Upstream 模式后两者称为Downstream 模式。此外对于支持 Key Manager 的芯片SOC_KEY_MANAGER_SUPPORTEDHMAC 模块还可以使用存储在 Key Manager 中的秘密密钥。hmac_key_id_t枚举中为此预留了HMAC_KEY_KM 7的槽位见 hal/hmac_types.h并提供了配套示例 examples/security/key_manager/。eFuse 密钥块与 Key Purpose防止密钥被挪作他用共有六个物理 eFuse 块可作为 HMAC 模块的密钥block 4 ~ block 9。API 中的枚举hmac_key_id_t将它们映射为HMAC_KEY0~HMAC_KEY5见 hal/hmac_types.h。每个密钥块还有一个对应的 eFuse 参数key purpose密钥用途用于限定该密钥允许用于哪种 HMAC 应用场景。这样做的目的是防止一个密钥被用于其原本用途之外的其它功能。对于支持 RSA_DS 的芯片映射关系如下Key Purpose应用场景8HMAC 为软件使用而生成Upstream7HMAC 作为 RSA 数字签名外设RSA_DS的密钥6HMAC 用于使能被软禁用的 JTAG 接口5HMAC 同时作为 RSA_DS 模块的密钥以及用于使能 JTAG对于不支持 RSA_DS 的芯片Key Purpose应用场景8HMAC 为软件使用而生成Upstream5, 6HMAC 用于使能被软禁用的 JTAG 接口HMAC Downstream 模式计算 HMAC 时软件需要提供包含秘密密钥的密钥块 ID以及密钥用途。在计算开始前HMAC 模块会查询所提供密钥块的 purpose只有 eFuse 中存储的 purpose 与请求的用途匹配时计算才会继续进行——这一校验逻辑体现在 HAL 的hmac_hal_configure()中当配置的目标hmac_hal_output_t与对应密钥槽的 purpose 不匹配时该函数会返回非零值表示配置失败见 hal/hmac_hal.h。应用场景一为软件生成 HMACUpstreamKey Purpose 8在此场景下HMAC 结果直接交给软件使用例如用于认证一条消息。计算 HMAC 的 API 是psa_mac_compute它接收一个opaque不透明PSA 密钥该密钥引用一个包含秘密、且 purpose 被设置为 Upstream 模式的 eFuse 密钥块。在 ROM 层面对应的底层函数是ets_hmac_calculate_message()——upstream HMAC 密钥对应ETS_EFUSE_KEY_PURPOSE_HMAC_UP函数签名为int ets_hmac_calculate_message(ets_efuse_block_t key_block, const void *message, size_t message_len, uint8_t *hmac);见 esp_rom/esp32s3/include/esp32s3/rom/hmac.h各支持芯片的components/esp_rom/chip/include/chip/rom/hmac.h均有同名实现。PSA 层的 opaque 驱动实现位于 components/mbedtls/port/psa_driver/esp_mac/psa_crypto_driver_esp_hmac_opaque.c其配套头文件定义了 ESP 硬件 HMAC 密钥的专属生命周期宏与导入/计算接口见 psa_crypto_driver_esp_hmac_opaque.hPSA_KEY_LOCATION_ESP_HMAC厂商专属位置0x800002厂商标志 ESP 厂商 IDPSA_KEY_LIFETIME_ESP_HMAC/PSA_KEY_LIFETIME_ESP_HMAC_VOLATILE默认持久化/易失的密钥生命周期esp_hmac_import_key_opaque()导入一个指向 eFuse/Key Manager 中密钥的引用而非真实密钥材料esp_hmac_compute_opaque()一次性完成整条消息的 MAC 计算。应用场景二HMAC 作为 RSA 数字签名外设的密钥DownstreamKey Purpose 7 / 5HMAC 可以被用作密钥派生函数解密 RSA 数字签名模块使用的私钥参数。此时由硬件使用一条标准消息standard message软件只需在 HMAC 侧提供 eFuse 密钥块与 purpose其余参数由 RSA 数字签名组件提供。关键安全特性是密钥与实际的 HMAC 值都不会暴露在 HMAC 模块和 RSA_DS 组件之外HMAC 的计算及其到 RSA_DS 组件的交接全部在内部完成。这对应 HAL 中的HMAC_OUTPUT_DS目标并配合hmac_hal_clean()在计算后清除失效交付给其他硬件的 HMAC 结果。应用场景三HMAC 用于使能软禁用的 JTAGDownstreamKey Purpose 6 / 5第三种应用是当 JTAG 被软禁用soft-disabled之后用 HMAC 作为密钥重新使能 JTAG。下面是从软禁用状态恢复 JTAG 的完整流程。Stage 1Setup一次性配置生成一个 256 位的 HMAC 密钥用于 JTAG 重新使能将该密钥写入一个Key Purpose 为HMAC_DOWN_ALL(5) 或HMAC_DOWN_JTAG(6)的 eFuse 块。可通过固件中的esp_efuse_write_key()完成也可在主机端使用idf.py efuse-burn-key完成使用esp_efuse_set_read_protect()将 eFuse 密钥块配置为读保护使软件无法读回密钥值烧写芯片上的soft JTAG disable位/位组。这会永久禁用 JTAG除非软件提供正确的密钥值。关于第 4 步的烧写 API不同芯片有所区别在 ESP32-S2 上使用esp_efuse_write_field_bit(ESP_EFUSE_SOFT_DIS_JTAG)烧写单个 soft JTAG disable 位在其他芯片上使用esp_efuse_write_field_cnt(ESP_EFUSE_SOFT_DIS_JTAG, ESP_EFUSE_SOFT_DIS_JTAG[0]-bit_count)烧写 soft JTAG disable 位组。同时需要留意一个前提条件在 ESP32-S2/ESP32-S3 上如果HARD_DIS_JTAGeFuse 已置位则SOFT_DIS_JTAG功能失效JTAG 已被永久禁用在其他芯片上如果DIS_PAD_JTAGeFuse 已置位则SOFT_DIS_JTAG功能同样失效。Stage 2重新使能 JTAG重新使能 JTAG 的密钥值是使用 eFuse 中的秘密密钥、以32 个0x00字节作为消息计算得到的 HMAC-SHA256 输出在固件中调用esp_hmac_jtag_enable时传入该密钥值要在固件中重新禁用 JTAG则复位系统或调用esp_hmac_jtag_disable。完整工作流示例hmac_soft_jtag仓库提供了完整的软禁用与重新使能 JTAG 示例 examples/security/hmac_soft_jtag/支持 ESP32-C3/C5/C6/H2/H21/H4/P4/S2/S3/S31 等芯片。其操作流程如下Step 1检查 JTAG 状态pip install -r requirements.txt python jtag_example_helper.py check_jtag_statusStep 2生成 32 字节 HMAC 密钥python jtag_example_helper.py generate_hmac_key KEY_FILE.binStep 3烧写 eFusepurpose 可选HMAC_DOWN_ALL或HMAC_DOWN_JTAG先用idf.py efuse-summary确认空闲密钥块espefuse -p $ESPPORT burn-key KEY_BLOCK_NO KEY_FILE.bin HMAC_DOWN_ALLStep 4从密钥生成 token 数据供重新使能 JTAG 时使用python jtag_example_helper.py generate_token KEY_FILE.bin OUTPUT_FILE(可选)之后在idf.py menuconfig的Example Configuration key block to be used中配置要使用的密钥块默认 -1 表示自动查找 purpose 为HMAC_DOWN_ALL或HMAC_DOWN_JTAG的第一个密钥编译烧录后进入控制台idf.py -p PORT flash monitor在控制台中用 token 重新使能 JTAG、或临时禁用 JTAGenable_jtag b2a49b1cce1be922bb7e431277413e3e8e6c3e8e6e17625c50ac66a9a857949b disable_jtag应用示例写入 eFuse 密钥并通过 PSA 计算软件 HMAC官方文档给出了一个完整的应用框架outline下面结合仓库代码展开。第一步用 eFuse 存储 HMAC 密钥使用esp_efuse_write_key将物理密钥块 4 设置为 HMAC 模块使用同时写入其 purpose。ESP_EFUSE_KEY_PURPOSE_HMAC_UP(8) 表示该密钥只能用于为软件生成 HMAC#include esp_efuse.h const uint8_t key_data[32] { ... }; esp_err_t status esp_efuse_write_key(EFUSE_BLK_KEY4, ESP_EFUSE_KEY_PURPOSE_HMAC_UP, key_data, sizeof(key_data)); if (status ESP_OK) { // written key } else { // writing key failed, maybe written already }esp_efuse_write_key的完整签名见 components/efuse/include/esp_efuse.h为esp_err_t esp_efuse_write_key(esp_efuse_block_t block, esp_efuse_purpose_t purpose, const void *key, size_t key_size_bytes);写入失败通常意味着该块已被写入过eFuse 只能烧写一次因此示例代码将其视为正常分支处理。第二步通过 PSA Crypto API 使用 eFuse 密钥计算 HMAC现在可以通过 PSA Crypto API用刚保存的密钥为软件计算 HMAC。这里的关键在于使用ESP-HMAC opaque 驱动导入的不是真实密钥材料而是指向 eFuse 密钥块的引用esp_hmac_opaque_key_t#include psa/crypto.h #include psa_crypto_driver_esp_hmac_opaque.h uint8_t hmac[32]; size_t hmac_length 0; const char *message Hello, HMAC!; const size_t msg_len 12; // Setup key attributes for ESP-HMAC opaque driver psa_key_attributes_t attributes PSA_KEY_ATTRIBUTES_INIT; psa_set_key_usage_flags(attributes, PSA_KEY_USAGE_SIGN_MESSAGE); psa_set_key_algorithm(attributes, PSA_ALG_HMAC(PSA_ALG_SHA_256)); psa_set_key_type(attributes, PSA_KEY_TYPE_HMAC); psa_set_key_bits(attributes, 256); psa_set_key_lifetime(attributes, PSA_KEY_LIFETIME_ESP_HMAC_VOLATILE); // Create opaque key reference for eFuse-based key esp_hmac_opaque_key_t opaque_key { .efuse_key_id HMAC_KEY4, }; // Import the opaque key psa_key_id_t key_id 0; psa_status_t status psa_import_key(attributes, (uint8_t *)opaque_key, sizeof(opaque_key), key_id); if (status ! PSA_SUCCESS) { // Failed to import key psa_reset_key_attributes(attributes); return; } // Compute HMAC status psa_mac_compute(key_id, PSA_ALG_HMAC(PSA_ALG_SHA_256), (uint8_t *)message, msg_len, hmac, sizeof(hmac), hmac_length); // Clean up psa_destroy_key(key_id); psa_reset_key_attributes(attributes); if (status PSA_SUCCESS) { // HMAC written to hmac now } else { // failure calculating HMAC }这里逐参数说明各配置项的语义PSA_KEY_USAGE_SIGN_MESSAGE允许使用该密钥生成消息认证码PSA_ALG_HMAC(PSA_ALG_SHA_256)算法为 HMAC-SHA256PSA_KEY_TYPE_HMAC与 256 位长度对应 32 字节的 eFuse 密钥PSA_KEY_LIFETIME_ESP_HMAC_VOLATILE密钥位于 ESP HMAC 专属位置0x800002且生命周期为易失——只在本操作期间有效不写入非易失存储esp_hmac_opaque_key_t.efuse_key_id HMAC_KEY4指明使用 eFuse 密钥块 4。该例程对应的回归测试可参考 components/mbedtls/test_apps/mbedtls_ut/main/test_psa_hmac.c 与 components/esp_security/test_apps/crypto_drivers/main/hmac_test_cases.h。硬件的一次性计算限制One-shot Limitation不支持流式分段使用 ESP-HMAC opaque PSA 驱动时必须注意底层硬件的工作方式该驱动由一次性one-shot的硬件 HMAC 外设支撑它会在单次操作中完成整条消息的 MAC 计算且无法在两次调用之间保存或恢复中间状态。因此不支持多段流式multipart streaming计算必须在一次psa_mac_compute调用中提供完整消息如上例所示或在单次 multipart update 中提供在同一操作上第二次非空 update 会返回PSA_ERROR_BAD_STATE操作会失败关闭fail closed永远不会产出只覆盖消息一部分的 MAC。这一特性也反映在 HAL 层的接口设计上hmac_hal_write_one_block_512()写入一个 512 位单块消息hmac_hal_write_block_512()/hmac_hal_next_block_normal()/hmac_hal_next_block_padding()则用于分块写入与填充块管理最终由hmac_hal_read_result_256()读出 256 位结果见 hal/hmac_hal.h。对多块消息驱动会在内部一次性完成所有块的写入与填充对调用方而言仍是一次调用、一次结果。从 HAL 到 ROM 的调用链一览HMAC 的完整软件栈可以分为三层便于读者按需查阅仓库源码PSA 驱动层应用可编程接口components/mbedtls/port/psa_driver/esp_mac/psa_crypto_driver_esp_hmac_opaque.c提供 opaque 密钥导入与psa_mac_compute的一站式实现HAL 层片上外设抽象components/esp_hal_security/include/hal/hmac_hal.h 与 components/esp_hal_security/hmac_hal.c提供hmac_hal_start()、hmac_hal_configure()、分块写入与结果读取等原语各芯片的寄存器层实现位于components/esp_hal_security/chip/include/hal/hmac_ll.hROM 层最底层硬件驱动各芯片的components/esp_rom/chip/include/chip/rom/hmac.h提供ets_hmac_enable()/ets_hmac_disable()、upstream 的ets_hmac_calculate_message()、downstream 的ets_hmac_calculate_downstream()及结果失效函数。上述调用链可以归纳为psa_mac_compute→ opaque 驱动 → HALhmac_hal_*→ ROMets_hmac_*→ HMAC 寄存器如components/soc/esp32c6/register/soc/hmac_reg.h。整体架构与本文描述的 Upstream/Downstream 模式、eFuse purpose 校验流程完全对应。小结在 ESP-IDF 中HMAC 并非一个普通的软件哈希库而是一个与 eFuse、Key Manager、RSA 数字签名外设及 JTAG 安全策略深度绑定的硬件安全模块。掌握它的关键在于理解三条主线密钥管理密钥必须写入 eFuse 物理块block 4~9并通过 Key Purpose 限定其唯一用途防止密钥越权使用双模式输出Upstream 模式将 HMAC 交给软件PSA opaque 驱动 psa_mac_computeDownstream 模式则将 HMAC 内部交付给 RSA_DS 或 JTAG 使能逻辑全程不暴露密钥硬件约束一次性计算、不支持流式分段调用时必须一次传入完整消息。在此基础上配合仓库中的 hmac_soft_jtag 示例、PSA opaque 驱动头文件 与 HAL 接口即可在自己的项目里落地一套基于硬件保护的 HMAC 认证与 JTAG 安全管控方案。【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考