# 3.2\_Security\_对称密钥算法之MAC（CMAC/HMAC）

## 3.2\_Security\_对称密钥算法之MAC（CMAC + HMAC）

Digest仅仅能够验证信息的**完整性**（Integrity），而没有办法确认信息的**身份认证**（Authetication）。例如，Alice收到一则需要Bob汇款1000元的消息，同时收到汇款消息的hash值，Alice可以计算hash确认1000元这个消息没有被篡改，但问题是，这则消息不是由Bob发的呢？因此就需要更高明的方法来帮助确认这个消息没有被篡改，还要确认这个消息是Bob发的。

消息认证有着广泛的应用，例如：

* **SWIFT**：环球银行金融电信协会。银行和银行之间通过SWIFT来传递交易消息，而消息的完整性及对消息的验证都是通过消息验证码。
* **IPSec**：互联网基本通信息协议，对通信内容认证和校验都是采用消息验证码。
* **SSL/TLS**：通信内容的认证和完整性校验都是通过消息验证码。

常用的MAC有：

* [CBC-MAC](https://en.wikipedia.org/wiki/CBC-MAC)
* [DAA](https://en.wikipedia.org/wiki/Data_Authentication_Algorithm)
* [GMAC](https://en.wikipedia.org/wiki/Galois_Message_Authentication_Code)
* [HMAC](https://en.wikipedia.org/wiki/HMAC)
* [NMAC](https://en.wikipedia.org/wiki/NMAC)
* [OMAC/CMAC](https://en.wikipedia.org/wiki/One-key_MAC)
* [PMAC](https://en.wikipedia.org/wiki/PMAC_\(cryptography\))
* [Poly1305](https://en.wikipedia.org/wiki/Poly1305)
* [SipHash](https://en.wikipedia.org/wiki/SipHash)
* [UMAC](https://en.wikipedia.org/wiki/UMAC)
* [VMAC](https://en.wikipedia.org/wiki/VMAC)

## 1. 概念

在密码学中，**消息认证码**（英语：Message authentication code，缩写为MAC），又译为**消息鉴别码**、**文件消息认证码**、**讯息鉴别码**、**信息认证码**，是经过特定算法后产生的一小段信息，检查某段消息的[完整性](https://zh.m.wikipedia.org/wiki/%E5%AE%8C%E6%95%B4%E6%80%A7)，以及作[身份验证](https://zh.m.wikipedia.org/wiki/%E8%BA%AB%E4%BB%BD%E9%AA%8C%E8%AF%81)。它可以用来检查在消息传递过程中，其内容是否被更改过，不管更改的原因是来自意外或是蓄意攻击。同时可以作为消息来源的[身份验证](https://zh.m.wikipedia.org/wiki/%E8%BA%AB%E4%BB%BD%E9%AA%8C%E8%AF%81)，确认消息的来源。

这里是一个消息认证码的实例，发送消息的人把**明文消息**和**密钥**送入MAC模块中会得到MAC，接收的人使用相同的密钥和明文消息进行计算，然后对MAC值进行对比，就可以确定这个消息是不是发送方发的。需要注意，**两方确认身份的凭证和对称加密算法一样，都是通过共享密钥来确认身份的**，所以在MAC里面也存在和SYM加解密一样的问题——**密钥配送的问题**（例如，公钥密码、Diffie-Hellman密钥交换、密钥分配中心等）

<div align="center"><img src="https://raw.githubusercontent.com/carloscn/images/main/typora20221027121621.png" alt="" width="85%"></div>

### 1.1 消息认证码的实现

消息认证码的算法中，通常会使用[带密钥的散列函数](https://zh.m.wikipedia.org/wiki/%E9%87%91%E9%91%B0%E9%9B%9C%E6%B9%8A%E8%A8%8A%E6%81%AF%E9%91%91%E5%88%A5%E7%A2%BC)（HMAC），或者[块密码](https://zh.m.wikipedia.org/wiki/%E5%A1%8A%E5%AF%86%E7%A2%BC)

SHA-2之类的单向散列函数可以实现消息认证码，其中一种实现方法称为HMAC。使用AES之类的分组密码也可以实现消息认证码，将密钥作为消息认证码的共享密钥，使用固定的IV值，并用CBC模式将消息全部加密。

> Alice认为既然同样持有密钥，对称加密兼顾了消息验证码的功能，只要解密出明文，就可以作为消息验证码使用，Alice想的对吗？
>
> 1. 对称加密明文和密文长度是一致的，如果是一个加解密来确认，对于传输是有负担的，而消息认证码只有几个字节，方便传输；
> 2. 如果使用ECB模式，明文块和块之间没有依赖，中间攻击者很可能删除一个完整块或做其他手脚，导致收到信息残缺，此时完整性已经被破坏，这是无法容忍的。
> 3. 对于一些随机数或者密钥作为明文（人不可读的信息）的加密，当另一方解密的时候我们很难通过主观断言这就是我们想要的明文，因为对称解密解密错误也是输出一些人类无法识别的数据信息。
>
> 因此， Alice考虑的不完全正确。

### 1.2 认证加密

**信息鉴别码不能提供对信息的保密，若要同时实现保密认证，同时需要对信息进行加密**。2000年后，关于认证加密逐渐展开（AE或者AEAD）。认证加密是一种将对称密码、消息认证码结合的的技术，因此，同时满足了机密性、完整性和认证三大功能。

消息认证码我们将在 [3.3\_Security\_对称密钥算法之AEAD](https://github.com/carloscn/blog/issues/145) 来详细说明，主要包含GCM/GMAC之类的。

### 1.3 对MAC的攻击

利用MAC的一些特性，可以衍生出一些攻击，还需要一些其他手段来抵御这些攻击，提高可靠性和安全性。

#### 1.3.1 重放攻击

**重放攻击**（英语：replay attack，或称为**回放攻击**）是一种恶意或欺诈的重复或延迟有效数据的[网络](https://zh.wikipedia.org/wiki/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C)攻击形式。 这可以由发起者或由拦截数据并重新传输数据的[对手](https://zh.wikipedia.org/w/index.php?title=%E5%AF%B9%E6%89%8B_\(%E5%AF%86%E7%A0%81%E5%AD%A6\)\&action=edit\&redlink=1)来执行。这是“[中间人攻击](https://zh.wikipedia.org/wiki/%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB)”的一个较低级别版本。

<div align="center"><img src="https://raw.githubusercontent.com/carloscn/images/main/typora20221027131531.png" alt="" width="85%"></div>

Bob在接受多个重放攻击之后，导致Bob把自己的钱全部转给了Alice，而Alice就会很迷惑了。

对于抵抗重放攻击，我们通常有以下手段，本质上都是让每次的MAC值发生变化，即便是Mallory拿到了某一次的MAC值，也无法进行重放攻击。

* **使用序号**。约定每次发送消息赋予一个递增的序号，在计算MAC的时候将序号值也包含在内。这样Mallory无法计算序号变换的MAC，就没有办法进行攻击了。但这样会增加两人的通信成本，需要共同协商和维护序号。
* **使用时间戳**。约定在发送消息的时候包含当前的时间，如果接到以前的消息，即便是MAC正确也将其作为错误的消息处理，这样就能抵御重放攻击。但这样需要进行时间同步，而且需要考虑到通信的延迟，必须有一定的窗口期，而在窗口期内也是有被重放攻击的风险的。
* **加扰nonce**。约定在通信之前Alice向Bob发送一个随机数，之后Alice会加入这个随机数的MAC计算，Bob也同样做这样的计算。代价就是，增加了通信开销。

#### 1.3.2 推测密钥

和单向散列函数攻击一样，对消息认证码也可以进行暴力破解以及[生日攻击](https://zh.wikipedia.org/wiki/%E7%94%9F%E6%97%A5%E6%94%BB%E5%87%BB)。对于MAC来说，应保证不能根据MAC值推测出通信双方所使用的密钥。HMAC就是利用sha函数的单向性和抗碰撞性来保证无法根据MAC推测出密钥。

此外，密钥务必是由真随机数生成的，否则会被推测出来。

#### 1.3.3 MAC的局限性

因为双方共同持有相同的密钥，所以第三方证明无法确认谁是发送，谁是接受。也同样没有办法防止否认。

**第三方证明**

Bob再接收到Alice的请求汇款的消息的时候，Bob想向第三方Victor证明这条消息是由Alice发的，Bob是做不到的。因此Victor认为，因为密钥Bob和Alice同样持有，Bob可以自己编写一个假消息，然后做MAC计算。Victor可能会怀疑Bob说谎，而Bob也没有办法来证明。

**否认**

那同样的Alice真的发了消息，但是不承认自己发了，Alice说谎说是Bob自己编的消息然后算的MAC值，同样Bob也没办法来指出Alice在说谎。

因此，**消息验证码没有办法证明出两个持有密钥的人之间的消息传递。而这就要靠数字签名来完成**。

## 2. HMAC原理

**HMAC** (有时扩展为 英语：keyed-hash message authentication code, **金钥杂凑讯息鉴别码**, 或 英语：hash-based message authentication code，**杂凑讯息鉴别码**），是一种通过特别计算方式之后产生的[讯息鉴别码](https://zh.m.wikipedia.org/wiki/訊息鑑別碼)（MAC），使用[密码杂凑函数](https://zh.m.wikipedia.org/wiki/密碼雜湊函數)，同时结合一个加密金钥。它可以用来保证资料的完整性，同时可以用来作某个讯息的[身份验证](https://zh.m.wikipedia.org/wiki/身份验证)。

<div align="center"><img src="https://raw.githubusercontent.com/carloscn/images/main/typora20221105190219.png" alt="" width="60%"></div>

### 2.1 HMAC过程

HMAC有个比较大条的思路就是上面的图片，对消息做MD5或者HASH，求得单向散列，接着对单向散列的数据进行加密。思路是这样的，但是实际上比这个情况要复杂的多。

根據RFC 2104，HMAC的數學公式為：

<div align="center"><img src="https://raw.githubusercontent.com/carloscn/images/main/typora20221105190410.png" alt="" width="40%"></div>

其中：

* H为密码杂凑函数（如SHA家族）
* K为密钥（secret key）
* m是要认证的消息
* K'是从原始密钥K导出的另一个秘密密钥（如果K短于散列函数的输入块大小，则向右填充（Padding）零；如果比该块大小更长，则对K进行散列）
* || 代表串接
* ⊕ 代表异或（XOR）
* opad 是外部填充（0x5c5c5c…5c5c，一段十六进制常量）
* ipad 是内部填充（0x363636…3636，一段十六进制常量）

进行处理的过程，我们可以从：

<div align="center"><img src="https://raw.githubusercontent.com/carloscn/images/main/typora20221105190540.png" alt="" width="80%"></div>

KEY和IPAD和OPAD与message之间是有信息冗余的，然后经过两轮HASH算法之后得到的MAC，我们可以更清晰的从这个角度来看，message和key和i\_pad和o\_pad之间的信息冗余：

<div align="center"><img src="https://raw.githubusercontent.com/carloscn/images/main/typora20221105190732.png" alt="" width="80%"></div>

### 2.2 mbedtls示例

我们以HMAC384为例，使用mbedtls来完成这个例子：

<https://github.com/carloscn/cryptography/blob/master/modules/digest/src/mbedtls\\_hmac.c>

```C
int mbedtls_hmac_sha384(const unsigned char *key,
                        size_t key_byte_size,
                        const unsigned char *input,
                        size_t input_byte_size,
                        unsigned char output[48])
{

    int ret = 0, mret = 0;
    mbedtls_md_info_t *info = NULL;
    mbedtls_md_context_t ctx;

    if (key == NULL ||
        input == NULL ||
        output == NULL) {
        ret = ERROR_COMMON_INPUT_PARAMETERS;
        mbedtls_printf(" * input parameters error, returned %d, line: %d\n",
                       ret, __LINE__);
        goto finish;
    }

    if (key_byte_size == 0 ||
        input_byte_size == 0) {
        ret = ERROR_NONE;
        mbedtls_printf(" * key size or input size == 0, returned %d, line: %d\n",
                       ret, __LINE__);
        goto finish;
    }

    mbedtls_md_init(&ctx);

    info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA384);
    if (NULL == info) {
        ret = ERROR_CRYPTO_INIT_FAILED;
        mbedtls_printf(" * init failed, returned %d, line: %d\n",
                       ret, __LINE__);
        goto ctx_inited;
    }

    mret = mbedtls_md_setup(&ctx, info, 1);
    if (0 != mret) {
        ret = ERROR_CRYPTO_INIT_FAILED;
        mbedtls_printf(" * init failed, returned %d, line: %d\n",
                       ret, __LINE__);
        goto ctx_inited;
    }

    mret = mbedtls_md_hmac_starts(&ctx, key, key_byte_size);
    if (0 != mret) {
        ret = ERROR_CRYPTO_INIT_FAILED;
        mbedtls_printf(" * start failed, returned %d, line: %d\n",
                       ret, __LINE__);
        goto ctx_inited;
    }

    mret = mbedtls_md_hmac_update(&ctx, input, input_byte_size);
    if (0 != mret) {
        ret = ERROR_CRYPTO_ENCRYPT_FAILED;
        mbedtls_printf(" * update failed, returned %d, line: %d\n",
                       ret, __LINE__);
        goto ctx_inited;
    }

    mret = mbedtls_md_hmac_finish(&ctx, output);
    if (0 != mret) {
        ret = ERROR_CRYPTO_ENCRYPT_FAILED;
        mbedtls_printf(" * finish failed, returned %d, line: %d\n",
                       ret, __LINE__);
    }

ctx_inited:
    mbedtls_md_free(&ctx);
finish:
    return ret;
}
```

测试函数为：

<https://github.com/carloscn/cryptography/blob/master/testsuite/src/unit\\_test\\_mbedtls.c#L599>

```C
static void test_mbedtls_hmac_384(void)
{
    int ret = 0, i = 0;
    unsigned char key[] = {1,5,8,8,9,5,6,204,5,4,8,0,0,0,0};
    unsigned char input[] = {1,5,8,8,9,5,6,204,5,4,8,0,0,0,0};
    unsigned char output[48] = {0};

    ret = mbedtls_hmac_sha384(key,
                              sizeof(key)/sizeof(key[0]),
                              input,
                              sizeof(input)/sizeof(input[0]),
                              output);
    for (i = 0; i < 48; i ++) {
        mbedtls_printf("%x", output[i]);
    }
    mbedtls_printf("\n");
    CU_ASSERT_EQUAL(ret, 0);
}
```

## 3. CBC-MAC和CMAC

消息认证码还有另一大类就是CMAC（Cipher-Based Message Authentication Code，CMAC）。提到CMAC就不得不提一下CBC-MAC，CMAC是CBC-MAC的变体，CBC-MAC因为只能对固定长度的消息进行运算，因此存在安全问题，CMAC可以补足这个问题。CMAC最后的输出叫做tag。

### 3.1 CBC-MAC

**CBC-MAC**是在实践中应用非常广泛的standard message MAC。当message的长度固定时，CBC-MAC是安全的。CBC-MAC是最为广泛使用的消息认证算法之一，同时它也是一个ANSI标准（X9.17）。CBC-MAC实际上就是对消息使用CBC模式进行加密，取密文的最后一块作为认证码tag。

CBC的构造方法如下：

<div align="center"><img src="https://raw.githubusercontent.com/carloscn/images/main/typora20221108210554.png" alt="" width="80%"></div>

**CBC-MAC**和**CBC操作模式**是相似的但还是存在一些重要的区别：

1. CBC操作模式使用的初始化向量I V IVIV，这是其安全性的主要保证；但是CBC-MAC却没有用到IV（使用随机向量IV会变得不安全）。
2. 在CBC操作模式中所有的中间值都被输出作为密文的一部分；而CBC-MAC则只输出最终的tag 。如果CBC-MAC输出每一个则就不再安全。

由于有更好的MAC可以供选择，比如HMAC和OMAC，因此，CBC-MAC很难在一些密码库中看到身影。（至少mbedtls没有这个函数）

### 3.2 OMAC

**One-key MAC** (**OMAC**)是一种消息认证码。在定义上，OMAC分为OMAC1和OMAC2，其中OMAC1就是我们说的CMAC，这个定义是2005年NIST进行推荐的。

业界发现了CBC-MAC存在的一些安全问题（在消息不是等长的时候，破坏fixed -length），进而创建了密码型消息身份验证代码（Cipher-Based Message Authentication Code，CMAC）。CMAC提供与CBC-MAC相同类型的数据源身份验证和完整性，但在数学上更为安全。CMAC 是CBC-MAC 的一种变体，它被批准与AES和三重DES一起使用。

CMAC的核心也是借助CBC-MAC：

<div align="center"><img src="https://raw.githubusercontent.com/carloscn/images/main/typora20221109214933.png" alt="" width="100%"></div>

算法一共有三个密钥，K, K1, K2, 其中K1和K2可以由K导出。对于CMAC来说，有两种情况，第一种是数据长度恰好就是分组长度的整数倍，对于这种情况，使用K1和最后一个分组异或之后加密得出结果，另一种情况是数据长度不是分组的整数倍，这就要先padding到分组的整数倍，Padding方法是先添加1bit的1, 其余bit填充0, 直到数据满足分组的整数倍。

OMAC和AES的关系是：

<div align="center"><img src="https://raw.githubusercontent.com/carloscn/images/main/typora20221109215650.png" alt="" width="80%"></div>

mbedtls提供了CMAC的接口，可以使用CMAC接口来计算消息的tag。注，mbedtls需要在编译的时候使能CMAC。`CONFIG_MBEDTLS_MAC_CMAC_ENABLED`。

```C
int mbedtls_cmac_aes_128_ecb(const unsigned char *key, 
							 size_t key_byte_size,
                             const unsigned char *input, 
                             size_t input_byte_size,
                             unsigned char *output, 
                             size_t *output_byte_size)
{
    int rc = 0;     /* mbedtls layer error code */
    int ret = 0;    /* current layer error code */
    mbedtls_cipher_context_t ctx, *p_ctx = NULL;
    mbedtls_cipher_info_t *info = NULL;

    mbedtls_cipher_init(&ctx);
    p_ctx = &ctx;
    info = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB);
    rc = mbedtls_cipher_setup(p_ctx, info);

    rc = mbedtls_cipher_cmac_starts(p_ctx, key, 
								    BYTE_CONV_BITS_NUM(key_byte_size));

    rc = mbedtls_cipher_cmac_update(p_ctx, input, input_byte_size);

    rc = mbedtls_cipher_cmac_finish(p_ctx, output);

    *output_byte_size = mbedtls_cipher_get_key_bitlen(p_ctx)/8;

    if (NULL != p_ctx) {
        mbedtls_cipher_free(p_ctx);
    }
    return ret;
}
```

使用示例： <https://github.com/carloscn/cryptography/blob/master/modules/digest/src/mbedtls\\_cmac\\_exa.c>

## Ref

1. [CONFIG\_MBEDTLS\_MAC\_CMAC\_ENABLED](https://docs.zephyrproject.org/3.0.0/reference/kconfig/CONFIG_MBEDTLS_MAC_CMAC_ENABLED.html)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://carloss-organization-4.gitbook.io/tech/tech/security/crypto/3.2security-dui-chen-mi-yao-suan-fa-zhi-maccmachmac.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
