# 哈希消息认证码 (HMAC)

[English]

哈希消息认证码 (HMAC) 是一种安全的身份认证技术，支持使用预共享的密钥验证消息的真实性和完整性。利用烧录在 eFuse 块中的密钥，HMAC 可以为生成 SHA256-HMAC 提供硬件加速。

更多有关应用操作流程，及 HMAC 计算过程的详细信息，请参阅 ESP32-S2 技术参考手册 > HMAC 加速器 (HMAC) [PDF]。

## 通用应用方案

现有 A、B 双方，他们都需要验证对方发送消息的真实性和完整性。那么在开始发送消息前，双方应通过安全通道交换密钥。

要验证来自 A 的信息，B 可遵循以下步骤：

- A 计算要发送的消息的 HMAC。

- A 将消息及消息的 HMAC 一并发送给 B。

- B 自行计算所接收消息的 HMAC。

- B 检查接收到的 HMAC 是否与计算得出的 HMAC 匹配。

若两个 HMAC 匹配，消息为真。

但 HMAC 本身还可以应用于更多场景，如支持 HMAC 的挑战-应答协议，或作为更多安全模块（详见下文）的密钥输入等。

## ESP32-S2 上的 HMAC

在 ESP32-S2 HMAC 模块的 eFuse 中会烧录一个密钥，可将该 eFuse 密钥设置为禁止所有外部资源访问，避免密钥泄露。

此外，在 ESP32-S2 上的 HMAC 有以下三种应用场景：

1. HMAC 支持软件使用

2. HMAC 用作数字签名 (DS) 的密钥

3. HMAC 用于启用软禁用的 JTAG 接口

第一种应用场景称为 上行 模式，后两种应用场景称为 下行 模式。

### HMAC 的 eFuse 密钥

在 ESP32-S2 中，有六个物理 eFuse 块可用作 HMAC 的密钥，分别是块 4 到块 9。在 API 中，枚举类型 `hmac_key_id_t` 将这些块映射为 `HMAC_KEY0` ~ `HMAC_KEY5`。

每个密钥都有相应的 eFuse 参数 密钥功能 (key purpose)，决定密钥应用于 HMAC 的哪种应用场景。

密钥功能 | 应用场景
--- | ---
8 | HMAC 支持软件使用
7 | HMAC 用作数字签名 (DS) 的密钥
6 | HMAC 启用软禁用的 JTAG 接口
5 | HMAC 既用作数字签名 (DS) 的密钥，又用于启用 JTAG 接口

这样一来，可以确保密钥用于原定场景。

要计算 HMAC，软件必须同时提供包含密钥的密钥块 ID，以及 密钥功能 （详情请参阅 ESP32-S2 技术参考手册 > eFuse 控制器 (eFuse) [PDF])。

在进行 HMAC 密钥计算前，HMAC 会验证软件所提供密钥块的功能。在软件所提供 ID 的对应密钥块中，eFuse 存储了密钥块的功能。只有当软件所提供密钥块的功能与 eFuse 中存储的密钥块功能匹配，才会继续进行计算。

### HMAC 支持软件使用

密钥功能值：8

在此情况下，HMAC 支持软件使用，如验证消息真实性等。

API `esp_hmac_calculate()` 用于计算 HMAC。输入参数包括消息、消息长度以及包含密钥的 eFuse 密钥块 ID，且该密钥块的 eFuse 密钥功能设置为上行模式。

### HMAC 用作数字签名 (DS) 的密钥

密钥功能值：7、5

HMAC 可用作密钥派生函数，解码数字签名模块使用的私钥参数。在此情况下，硬件使用标准信息进行计算。在 HMAC 部分只需提供 eFuse 密钥块和功能；而在数字签名模块则还需要一些额外参数。

无论是密钥还是实际的 HMAC，都不会暴露在 HMAC 和数字签名模块之外。对 HMAC 的计算，以及将其传递给数字签名模块的过程，均在内部进行。

详情请参阅 ESP32-S2 技术参考手册 > 数字签名 (DS) [PDF]。

### HMAC 启用 JTAG 接口

密钥功能值：6、5

HMAC 的第三种应用场景是将其作为密钥，启用软禁用的 JTAG 接口。

重新启用 JTAG 接口的步骤如下：

第一步：设置

1. 生成一个 256 位的 HMAC 密钥，用于重新启用 JTAG。

2. 将步骤 1 获得的密钥写入 eFuse 块，且 eFuse 块的密钥功能参数应为 HMAC_DOWN_ALL (5) 或 HMAC_DOWN_JTAG (6)。为此，可以使用固件中的 `esp_efuse_write_key()` 函数，或使用主机上的 `idf.py efuse-burn-key` 完成操作。

3. 使用 `esp_efuse_set_read_protect()` 将 eFuse 密钥块配置为读保护，防止软件读取写入到 eFuse 密钥块中的 HMAC 密钥值。

4. 在烧录到 ESP32-S2 上时，将特定的位或位组设置为 `soft JTAG disable`。这样可以永久禁用 JTAG 接口，除非软件提供正确的密钥值进行验证。

备注

API esp_efuse_write_field_bit(ESP_EFUSE_SOFT_DIS_JTAG) 支持在 ESP32-S2 上烧录 `soft JTAG disable` 位。

备注

置位 `HARD_DIS_JTAG` eFuse 时，JTAG 处于永久禁用状态，`SOFT_DIS_JTAG` 功能将失效。

启用 JTAG

1. 以 eFuse 中的密钥和 32 个 `0x00` 字节为输入，经过 HMAC-SHA256 函数处理，得到的函数输出结果即重新启用 JTAG 的密钥。

2. 从固件调用 `esp_hmac_jtag_enable()` 函数时，传递上一步获取的密钥值。

3. 要在固件中重新禁用 JTAG，可以重置系统，或调用 `esp_hmac_jtag_disable()`。

关于如何软禁用以及重新启用 JTAG 的完整示例，请参考 security/hmac_soft_jtag。该示例演示了如何使用 HMAC 重新启用已软禁用的 JTAG 接口，包括生成 HMAC 密钥、将其烧录到 eFuse 中以及根据密钥创建令牌数据等步骤。

更多有关详情，请参阅 ESP32-S2 技术参考手册 > HMAC 加速器 (HMAC) [PDF]。

## 应用示例

以下为针对特定应用场景的实例代码，可用于设置 eFuse 密钥，并将其用于计算支持软件使用的 HMAC。

`esp_efuse_write_key` 可以设置 eFuse 中的物理密钥块 4，并设置其功能。`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) {
        // 密钥写入成功
    } else {
        // 密钥写入失败，可能已写入过
    }

接下来可以使用已存储的密钥来计算 HMAC，供软件使用。

    #include "esp_hmac.h"

    uint8_t hmac[32];

    const char *message = "Hello, HMAC!";
    const size_t msg_len = 12;

    esp_err_t result = esp_hmac_calculate(HMAC_KEY4, message, msg_len, hmac);

    if (result == ESP_OK) {
        // HMAC 已写入 hmac 数组
    } else {
        // 计算 HMAC 失败
    }

## API 参考

### Header File

- components/esp_security/include/esp_hmac.h

- This header file can be included with:

  >     #include "esp_hmac.h"

- This header file is a part of the API provided by the `esp_security` component. To declare that your component depends on `esp_security`, add the following to your CMakeLists.txt:

  >     REQUIRES esp_security

  > or

  >     PRIV_REQUIRES esp_security

### Functions

esp_err_t esp_hmac_calculate(hmac_key_id_t key_id, const void *message, size_t message_len, uint8_t *hmac)

  Calculate the HMAC of a given message.

  Calculate the HMAC `hmac` of a given message `message` with length `message_len`. SHA256 is used for the calculation.

  备注

  Uses the HMAC peripheral in "upstream" mode.

  参数:

    - key_id -- Determines which of the 6 key blocks in the efuses should be used for the HMAC calculation. The corresponding purpose field of the key block in the efuse must be set to the HMAC upstream purpose value.

    - message -- the message for which to calculate the HMAC

    - message_len -- message length return ESP_ERR_INVALID_STATE if unsuccessful

    - hmac -- [out] the hmac result; the buffer behind the provided pointer must be a writeable buffer of 32 bytes

  返回:

    - ESP_OK, if the calculation was successful,

    - ESP_ERR_INVALID_ARG if message or hmac is a nullptr or if key_id out of range

    - ESP_FAIL, if the hmac calculation failed

esp_err_t esp_hmac_jtag_enable(hmac_key_id_t key_id, const uint8_t *token)

  Use HMAC peripheral in Downstream mode to re-enable the JTAG, if it is not permanently disabled by HW. In downstream mode, HMAC calculations performed by peripheral are used internally and not provided back to user.

  备注

  Return value of the API does not indicate the JTAG status.

  参数:

    - key_id -- Determines which of the 6 key blocks in the efuses should be used for the HMAC calculation. The corresponding purpose field of the key block in the efuse must be set to HMAC downstream purpose.

    - token -- Pre calculated HMAC value of the 32-byte 0x00 using SHA-256 and the known private HMAC key. The key is already programmed to a eFuse key block. The key block number is provided as the first parameter to this function.

  返回:

    - ESP_OK, if the key_purpose of the key_id matches to HMAC downstread mode, The API returns success even if calculated HMAC does not match with the provided token. However, The JTAG will be re-enabled only if the calculated HMAC value matches with provided token, otherwise JTAG will remain disabled.

    - ESP_FAIL, if the key_purpose of the key_id is not set to HMAC downstream purpose or JTAG is permanently disabled by EFUSE_HARD_DIS_JTAG eFuse parameter.

    - ESP_ERR_INVALID_ARG, invalid input arguments

esp_err_t esp_hmac_jtag_disable(void)

  Disable the JTAG which might be enabled using the HMAC downstream mode. This function just clears the result generated by calling esp_hmac_jtag_enable() API.

  返回:

    - ESP_OK return ESP_OK after writing the HMAC_SET_INVALIDATE_JTAG_REG with value 1.

此文档对您有帮助吗？

- 反馈已收到，谢谢！
- 如果您有其他意见，欢迎填写乐鑫文档反馈表。

- 我们重视您的反馈。
- 您可以填写乐鑫文档反馈表告诉我们如何改进该文档。