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 对要发送的消息计算 HMAC;
- A 将消息与 HMAC 一并发送给 B;
- B 自己重新计算收到消息的 HMAC;
- B 比对收到的 HMAC 与本地计算出的 HMAC 是否一致。
若两者匹配,则消息是真实可信的。不过 HMAC 并不局限于这一种用法,它同样可以用于支持 HMAC 的挑战-响应协议(challenge-response protocol),或作为后续安全模块的密钥输入(详见下文 Downstream 模式)。
HMAC on ESP32:eFuse 中的密钥与 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_SUPPORTED),HMAC 模块对应两种/三种应用场景:
- HMAC 为软件使用而生成(Upstream 模式);
- HMAC 作为 RSA 数字签名外设(RSA_DS)的密钥(Downstream 模式,仅支持 RSA_DS 的芯片);
- HMAC 用于使能被软禁用的 JTAG 接口(Downstream 模式)。
第一种模式称为Upstream 模式,后两者称为Downstream 模式。
此外,对于支持 Key Manager 的芯片(SOC_KEY_MANAGER_SUPPORTED),HMAC 模块还可以使用存储在 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 | 应用场景 |
|---|---|
| 8 | HMAC 为软件使用而生成(Upstream) |
| 7 | HMAC 作为 RSA 数字签名外设(RSA_DS)的密钥 |
| 6 | HMAC 用于使能被软禁用的 JTAG 接口 |
| 5 | HMAC 同时作为 RSA_DS 模块的密钥以及用于使能 JTAG |
对于不支持 RSA_DS 的芯片:
| Key Purpose | 应用场景 |
|---|---|
| 8 | HMAC 为软件使用而生成(Upstream) |
| 5, 6 | HMAC 用于使能被软禁用的 JTAG 接口(HMAC Downstream 模式) |
计算 HMAC 时,软件需要提供包含秘密密钥的密钥块 ID以及密钥用途。在计算开始前,HMAC 模块会查询所提供密钥块的 purpose,只有 eFuse 中存储的 purpose 与请求的用途匹配时,计算才会继续进行——这一校验逻辑体现在 HAL 的hmac_hal_configure()中:当配置的目标(hmac_hal_output_t)与对应密钥槽的 purpose 不匹配时,该函数会返回非零值表示配置失败(见 hal/hmac_hal.h)。
应用场景一:为软件生成 HMAC(Upstream,Key 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.h):
PSA_KEY_LOCATION_ESP_HMAC:厂商专属位置0x800002(厂商标志 + ESP 厂商 ID);PSA_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 数字签名外设的密钥(Downstream,Key Purpose = 7 / 5)
HMAC 可以被用作密钥派生函数,解密 RSA 数字签名模块使用的私钥参数。此时由硬件使用一条标准消息(standard message),软件只需在 HMAC 侧提供 eFuse 密钥块与 purpose,其余参数由 RSA 数字签名组件提供。
关键安全特性是:密钥与实际的 HMAC 值都不会暴露在 HMAC 模块和 RSA_DS 组件之外,HMAC 的计算及其到 RSA_DS 组件的交接全部在内部完成。这对应 HAL 中的HMAC_OUTPUT_DS目标,并配合hmac_hal_clean()在计算后清除(失效)交付给其他硬件的 HMAC 结果。
应用场景三:HMAC 用于使能软禁用的 JTAG(Downstream,Key Purpose = 6 / 5)
第三种应用是:当 JTAG 被软禁用(soft-disabled)之后,用 HMAC 作为密钥重新使能 JTAG。下面是从软禁用状态恢复 JTAG 的完整流程。
Stage 1:Setup(一次性配置)
- 生成一个 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:烧写 eFuse(purpose 可选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、或临时禁用 JTAG:
enable_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-SHA256;PSA_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.h; - ROM 层(最底层硬件驱动):各芯片的
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 驱动 → HAL(hmac_hal_*)→ ROM(ets_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_compute),Downstream 模式则将 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),仅供参考