# 数字签名 (DS)

[English]

数字签名 (DS) 模块利用 RSA 硬件加速器为信息签名。HMAC 作为密钥派生函数，使用 eFuse 作为输入密钥，输出一组加密参数。随后，数字签名模块利用这组预加密的参数，计算出签名。以上过程均在硬件中完成，因此在计算签名时，软件无法获取 RSA 参数的解密密钥，也无法获取 HMAC 密钥派生函数的输入密钥。

签名计算所涉及的硬件信息以及所用寄存器的有关信息，请参阅 ESP32-C3 技术参考手册 > 数字签名 (DS) [PDF]。

## 私钥参数

RSA 签名的私钥参数存储在 flash 中。为防止发生未经授权的访问，这些参数都进行了 AES 加密。此时，HMAC 模块被作为密钥派生函数，用于计算 AES 加密了的私钥参数。同时，HMAC 模块本身使用的来自 eFuse 密钥块的密钥也具有读取保护，可以防止发生未经授权的访问。

调用签名计算时，软件只需指定使用的 eFuse 密钥、相应的 eFuse 密钥用途、加密的 RSA 参数位置以及待签名的数据或信息。

## 密钥生成

在使用 DS 外设前，需首先创建并存储 HMAC 密钥和 RSA 私钥，此步骤可在 ESP32-C3 上通过软件完成，也可在主机上进行。在 ESP-IDF 中，可以使用 `esp_efuse_write_block()` 设置 HMAC 密钥，并使用 `esp_hmac_calculate()` 对 RSA 私钥参数进行加密。

计算并组装私钥参数的详细信息，请参阅 ESP32-C3 技术参考手册 > 数字签名 (DS) [PDF]。

## 在 ESP-IDF 中进行数字签名计算

在 ESP-IDF 中进行数字签名计算的工作流程，以及所用寄存器的有关信息，请参阅 ESP32-C3 技术参考手册 > 数字签名 (DS) [PDF]。

要进行数字签名计算，需要准备以下三个参数：

1. 用作 HMAC 密钥的 eFuse 密钥块 ID

2. 加密私钥参数的位置

3. 待签名的数据或信息

由于签名计算需要一些时间，ESP-IDF 提供了两种可用的 API。第一种是 `esp_ds_sign()`，调用此 API 后，程序会在计算完成前保持阻塞状态。如果在计算过程中，软件需要执行其他操作，则可以调用 `esp_ds_start_sign()`，用另一种方式启动签名计算，然后周期性地调用 `esp_ds_is_busy()`，检查计算是否已完成。一旦计算完成，即可调用 `esp_ds_finish_sign()` 来获取签名结果。

API `esp_ds_sign()` 和 `esp_ds_start_sign()` 会借助 DS 外设计算明文 RSA 签名。RSA 签名需要转换成合适的格式，以供进一步使用。例如，MbedTLS SSL 栈支持 PKCS#1 格式，使用 API `esp_ds_rsa_sign()` 可以直接获得 PKCS#1 v1.5 格式的签名，该 API 内部调用了 `esp_ds_start_sign()` 函数，并将签名转换成 PKCS#1 v1.5 格式。

备注

此处只是最基本的 DS 构造块，其消息必须是固定长度。为在任意消息上创建签名，通常会将实际消息的哈希值作为输入，并将其填充到所需长度。乐鑫计划在未来提供一个 API 来实现这个功能。

## TLS 连接所需的 DS 外设配置

在 ESP32-C3 芯片上使用 TLS 连接之前，必须先配置 DS 外设，具体步骤如下：

1. 随机生成一个 256 位的值，作为 `初始化向量` (IV)。

2. 随机生成一个 256 位的值，作为 `HMAC_KEY`。

3. 使用客户端私钥 (RAS) 和上述步骤生成的参数，计算加密的私钥参数。

4. 将 256 位的 `HMAC_KEY` 烧录到 eFuse 中，只支持 DS 外设读取。

更多细节，请参阅 ESP32-C3 技术参考手册 > 数字签名 (DS) [PDF]。

如果要以开发为目的配置 DS 外设，可以使用 esp-secure-cert-tool。

配置完 DS 外设后获取的加密私钥参数需要保存在 flash 中并传递给 DS 外设，DS 外设将使用这些参数完成数字签名。随后，应用程序需要从 flash 中读取 DS 数据，这可以通过 esp_secure_cert_mgr 组件提供的 API 完成。更多细节，请参阅 component/README。

在 esp_tls 仓库内部，ESP-TLS 负责完成初始化 DS 外设、执行数字签名的过程。更多细节，请参阅 ESP-TLS 的数字签名。

如 ESP-TLS 文档所述，应用程序只需将加密私钥参数作为 ds_data 传递给 esp_tls 上下文，esp_tls 仓库内部就会执行所有必要操作，以初始化 DS 外设，并执行数字签名。

## 使用 DS 外设进行 SSL 双向认证

示例 protocols/mqtt/ssl_ds 展示了如何使用 DS 外设进行 SSL 双向认证。在示例中，使用了 mqtt_client （通过 ESP-MQTT 实现），通过 SSL 传输连接到代理服务器 `test.mosquitto.org`，并进行 SSL 双向认证。SSL 部分在内部使用 ESP-TLS 完成。更多细节，请参阅 protocols/mqtt/ssl_ds/README.md。

## API 参考

### Header File

- components/esp_hw_support/include/esp_ds.h

- This header file can be included with:

  >     #include "esp_ds.h"

### Functions

esp_err_t esp_ds_sign(const void *message, const esp_ds_data_t *data, hmac_key_id_t key_id, void *signature)

  Sign the message with a hardware key from specific key slot. The function calculates a plain RSA signature with help of the DS peripheral. The RSA encryption operation is as follows: Z = XY mod M where, Z is the signature, X is the input message, Y and M are the RSA private key parameters.

  This function is a wrapper around `esp_ds_finish_sign()` and `esp_ds_start_sign()`, so do not use them in parallel. It blocks until the signing is finished and then returns the signature.

  备注

  Please see note section of `esp_ds_start_sign()` for more details about the input parameters.

  参数:

    - message -- the message to be signed; its length should be (data->rsa_length + 1)*4 bytes, and those bytes must be in little endian format. It is your responsibility to apply your hash function and padding before calling this function, if required. (e.g. message = padding(hash(inputMsg)))

    - data -- the encrypted signing key data (AES encrypted RSA key + IV)

    - key_id -- the HMAC key ID determining the HMAC key of the HMAC which will be used to decrypt the signing key data

    - signature -- the destination of the signature, should be (data->rsa_length + 1)*4 bytes long

  返回:

    - ESP_OK if successful, the signature was written to the parameter `signature`.

    - ESP_ERR_INVALID_ARG if one of the parameters is NULL or data->rsa_length is too long or 0

    - ESP_ERR_HW_CRYPTO_DS_HMAC_FAIL if there was an HMAC failure during retrieval of the decryption key

    - ESP_ERR_NO_MEM if there hasn't been enough memory to allocate the context object

    - ESP_ERR_HW_CRYPTO_DS_INVALID_KEY if there's a problem with passing the HMAC key to the DS component

    - ESP_ERR_HW_CRYPTO_DS_INVALID_DIGEST if the message digest didn't match; the signature is invalid.

    - ESP_ERR_HW_CRYPTO_DS_INVALID_PADDING if the message padding is incorrect, the signature can be read though since the message digest matches.

esp_err_t esp_ds_start_sign(const void *message, const esp_ds_data_t *data, hmac_key_id_t key_id, esp_ds_context_t **esp_ds_ctx)

  Start the signing process.

  This function yields a context object which needs to be passed to `esp_ds_finish_sign()` to finish the signing process. The function calculates a plain RSA signature with help of the DS peripheral. The RSA encryption operation is as follows: Z = XY mod M where, Z is the signature, X is the input message, Y and M are the RSA private key parameters.

  备注

  This function locks the HMAC, SHA, AES and RSA components, so the user has to ensure to call `esp_ds_finish_sign()` in a timely manner. The numbers Y, M, Rb which are a part of esp_ds_data_t should be provided in little endian format and should be of length equal to the RSA private key bit length The message length in bits should also be equal to the RSA private key bit length. No padding is applied to the message automatically, Please ensure the message is appropriate padded before calling the API.

  参数:

    - message -- the message to be signed; its length should be (data->rsa_length + 1)*4 bytes, and those bytes must be in little endian format. It is your responsibility to apply your hash function and padding before calling this function, if required. (e.g. message = padding(hash(inputMsg)))

    - data -- the encrypted signing key data (AES encrypted RSA key + IV)

    - key_id -- the HMAC key ID determining the HMAC key of the HMAC which will be used to decrypt the signing key data

    - esp_ds_ctx -- the context object which is needed for finishing the signing process later

  返回:

    - ESP_OK if successful, the ds operation was started now and has to be finished with `esp_ds_finish_sign()`

    - ESP_ERR_INVALID_ARG if one of the parameters is NULL or data->rsa_length is too long or 0

    - ESP_ERR_HW_CRYPTO_DS_HMAC_FAIL if there was an HMAC failure during retrieval of the decryption key

    - ESP_ERR_NO_MEM if there hasn't been enough memory to allocate the context object

    - ESP_ERR_HW_CRYPTO_DS_INVALID_KEY if there's a problem with passing the HMAC key to the DS component

bool esp_ds_is_busy(void)

  Return true if the DS peripheral is busy, otherwise false.

  备注

  Only valid if `esp_ds_start_sign()` was called before.

esp_err_t esp_ds_finish_sign(void *signature, esp_ds_context_t *esp_ds_ctx)

  Finish the signing process.

  参数:

    - signature -- the destination of the signature, should be (data->rsa_length + 1)*4 bytes long, the resultant signature bytes shall be written in little endian format.

    - esp_ds_ctx -- the context object retreived by `esp_ds_start_sign()`

  返回:

    - ESP_OK if successful, the ds operation has been finished and the result is written to signature.

    - ESP_ERR_INVALID_ARG if one of the parameters is NULL

    - ESP_ERR_HW_CRYPTO_DS_INVALID_DIGEST if the message digest didn't match; the signature is invalid. This means that the encrypted RSA key parameters are invalid, indicating that they may have been tampered with or indicating a flash error, etc.

    - ESP_ERR_HW_CRYPTO_DS_INVALID_PADDING if the message padding is incorrect, the signature can be read though since the message digest matches (see TRM for more details).

esp_err_t esp_ds_encrypt_params(esp_ds_data_t *data, const void *iv, const esp_ds_p_data_t *p_data, const void *key)

  Encrypt the private key parameters.

  The encryption is a prerequisite step before any signature operation can be done. It is not strictly necessary to use this encryption function, the encryption could also happen on an external device.

  备注

  The numbers Y, M, Rb which are a part of esp_ds_data_t should be provided in little endian format and should be of length equal to the RSA private key bit length The message length in bits should also be equal to the RSA private key bit length. No padding is applied to the message automatically, Please ensure the message is appropriate padded before calling the API.

  参数:

    - data -- Output buffer to store encrypted data, suitable for later use generating signatures.

    - iv -- Pointer to 16 byte IV buffer, will be copied into 'data'. Should be randomly generated bytes each time.

    - p_data -- Pointer to input plaintext key data. The expectation is this data will be deleted after this process is done and 'data' is stored.

    - key -- Pointer to 32 bytes of key data. Type determined by key_type parameter. The expectation is the corresponding HMAC key will be stored to efuse and then permanently erased.

  返回:

    - ESP_OK if successful, the ds operation has been finished and the result is written to signature.

    - ESP_ERR_INVALID_ARG if one of the parameters is NULL or p_data->rsa_length is too long

### Structures

struct esp_digital_signature_data

  Encrypted private key data. Recommended to store in flash in this format.

  备注

  This struct has to match to one from the ROM code! This documentation is mostly taken from there.

  Public Members

  esp_digital_signature_length_t rsa_length

    RSA LENGTH register parameters (number of words in RSA key & operands, minus one).

    This value must match the length field encrypted and stored in 'c', or invalid results will be returned. (The DS peripheral will always use the value in 'c', not this value, so an attacker can't alter the DS peripheral results this way, it will just truncate or extend the message and the resulting signature in software.)

    备注

    In IDF, the enum type length is the same as of type unsigned, so they can be used interchangably. See the ROM code for the original declaration of struct `ets_ds_data_t`.

  uint32_t iv[ESP_DS_IV_BIT_LEN / 32]

    IV value used to encrypt 'c'

  uint8_t c[ESP_DS_C_LEN]

    Encrypted Digital Signature parameters. Result of AES-CBC encryption of plaintext values. Includes an encrypted message digest.

struct esp_ds_p_data_t

  Plaintext parameters used by Digital Signature.

  This is only used for encrypting the RSA parameters by calling esp_ds_encrypt_params(). Afterwards, the result can be stored in flash or in other persistent memory. The encryption is a prerequisite step before any signature operation can be done.

  备注

  Y, M, Rb, & M_Prime must all be in little endian format.

  Public Members

  uint32_t Y[ESP_DS_SIGNATURE_MAX_BIT_LEN / 32]

    RSA exponent.

  uint32_t M[ESP_DS_SIGNATURE_MAX_BIT_LEN / 32]

    RSA modulus.

  uint32_t Rb[ESP_DS_SIGNATURE_MAX_BIT_LEN / 32]

    RSA r inverse operand.

  uint32_t M_prime

    RSA M prime operand.

  uint32_t length

    RSA length in words (32 bit)

### Macros

ESP_DS_IV_BIT_LEN

ESP_DS_IV_LEN

ESP_DS_SIGNATURE_MAX_BIT_LEN

ESP_DS_SIGNATURE_MD_BIT_LEN

ESP_DS_SIGNATURE_M_PRIME_BIT_LEN

ESP_DS_SIGNATURE_L_BIT_LEN

ESP_DS_SIGNATURE_PADDING_BIT_LEN

ESP_DS_C_LEN

### Type Definitions

typedef struct esp_ds_context esp_ds_context_t

typedef struct esp_digital_signature_data esp_ds_data_t

  Encrypted private key data. Recommended to store in flash in this format.

  备注

  This struct has to match to one from the ROM code! This documentation is mostly taken from there.

### Enumerations

enum esp_digital_signature_length_t

  Values:

  enumerator ESP_DS_RSA_1024
  enumerator ESP_DS_RSA_2048
  enumerator ESP_DS_RSA_3072
  enumerator ESP_DS_RSA_4096

提供有关此文档的反馈