MAC 消息认证码:同时验证来源和完整性

MAC 是 Message Authentication Code ,即消息认证码

它的核心作用是同时验证:

  1. 消息完整性:消息有没有被篡改;
  2. 消息来源:消息是否来自持有正确密钥的一方。

相比单独 Hash,MAC 引入了密钥。因此,攻击者即使知道算法,也无法在没有密钥的情况下伪造合法认证码。

关键词 工程含义
MAC / HMAC 带密钥的摘要可同时验证来源和完整性。

1. MAC 是什么

MAC,全称是 Message Authentication Code ,中文通常翻译为消息认证码

它可以理解为:

使用密钥参与计算的消息摘要。

普通 Hash 只对消息内容计算摘要,任何人都可以计算。

而 MAC 不仅使用消息内容,还会使用一个只有通信双方知道的密钥。

因此,MAC 不只是判断"数据有没有变",还可以判断:

发送方是否拥有正确密钥。


2. MAC 解决什么问题

在嵌入式系统中,MAC 主要解决两个问题。

2.1 验证完整性

完整性解决的是:

消息在传输或存储过程中有没有被修改?

如果攻击者修改了消息内容,但不知道密钥,就无法计算出正确的 MAC。

接收方重新计算 MAC 后,会发现结果不一致,从而拒绝这条消息。


2.2 验证来源

来源验证解决的是:

这条消息是不是合法对象发来的?

因为只有持有共享密钥的一方才能计算出正确 MAC,所以 MAC 可以证明发送方大概率拥有正确密钥。

这比单独 Hash 更安全。

单独 Hash 只能说明:

消息和摘要匹配。

而 MAC 可以进一步说明:

消息和认证码匹配,并且认证码是用正确密钥计算出来的。


3. MAC 的基本思想

MAC 的基本流程如下:

  1. 发送方准备消息内容;
  2. 发送方使用共享密钥和消息内容计算 MAC;
  3. 发送方把消息和 MAC 一起发送给接收方;
  4. 接收方使用同一密钥重新计算 MAC;
  5. 接收方把自己计算出的 MAC 与收到的 MAC 进行比较;
  6. 如果一致,说明验证通过;
  7. 如果不一致,说明消息可能被篡改,或者发送方不可信。

可以简化理解为:

复制代码
发送方:
MAC = MAC_Algorithm(Key, Message)

接收方:
Expected_MAC = MAC_Algorithm(Key, Received_Message)

if Expected_MAC == Received_MAC:
    接受消息
else:
    拒绝消息

4. MAC 与 Hash 的区别

对比项 Hash MAC
是否使用密钥
是否能发现数据变化
是否能证明来源 不能 能,前提是密钥安全
是否任何人都能计算
典型算法 SHA-256 HMAC-SHA256、AES-CMAC
典型用途 文件摘要、固件摘要 消息认证、挑战响应、设备认证

Hash 没有密钥参与,因此攻击者修改消息后,可以重新计算 Hash。

MAC 有密钥参与,攻击者即使修改消息,也无法在不知道密钥的情况下生成正确 MAC。


5. HMAC-SHA256

工程上常见的 MAC 实现是 HMAC-SHA256

HMAC 的全称是 Hash-based Message Authentication Code,也就是基于 Hash 的消息认证码。

HMAC-SHA256 可以理解为:

使用 SHA-256 作为底层 Hash 算法,并按照 HMAC 规范结构引入密钥。

需要注意的是,HMAC-SHA256 不是简单地做:

复制代码
Hash(Key + Message)

也不是简单地做:

复制代码
Hash(Message + Key)

而是有一套标准化结构。

这样设计是为了避免简单拼接带来的安全风险。

因此,在 MCU 项目中,如果软件实现 MAC,应优先使用成熟密码库中的 HMAC-SHA256,而不是自行设计变体。


6. AES-CMAC

除了 HMAC-SHA256,嵌入式系统中也可能使用 AES-CMAC

AES-CMAC 是基于 AES 分组密码构造的消息认证码。

它适合已经具备 AES 硬件加速模块的 MCU 或安全芯片。

常见选择可以这样理解:

场景 推荐考虑
MCU 有 SHA 硬件加速 HMAC-SHA256
MCU 有 AES 硬件加速 AES-CMAC
安全芯片提供固定认证接口 优先使用芯片支持的 MAC 算法
软件资源有限 选择成熟库中资源占用可接受的实现

具体项目中应根据芯片能力、性能、库支持、认证协议和安全等级综合选择。


7. Challenge-Response 挑战响应

MAC 常用于挑战响应认证。

挑战响应要解决的问题是:

如何证明设备拥有正确密钥,但又不把密钥发出来?

典型流程如下:

  1. 主机生成随机数 Challenge;
  2. 主机把 Challenge 发送给设备;
  3. 设备使用内部密钥对 Challenge 计算 MAC;
  4. 设备返回 Response;
  5. 主机使用相同密钥计算期望 Response;
  6. 主机比较设备返回值和本地计算值;
  7. 如果一致,认证通过。

流程可以表示为:

复制代码
Host 生成 Challenge
Host → Device: Challenge

Device:
Response = HMAC(Key, Challenge)

Device → Host: Response

Host:
Expected = HMAC(Key, Challenge)

if Response == Expected:
    认证通过
else:
    认证失败

8. 为什么挑战响应可以防重放

如果设备每次认证都返回固定口令,攻击者只要抓包一次,就可以后续重复发送同样的数据。

这就是重放攻击。

同时截取 Challenge 和 Response 后,是否能重放,取决于 Challenge 是否具备一次性、随机性和上下文绑定。

如果 Challenge 每次由主机重新生成、不可预测、不重复,并且只在当前会话中有效,那么旧的 Challenge + Response 不能直接重放。

如果 Challenge 固定、重复、可预测,或者主机允许验证旧 Challenge,那么攻击者截取到一组 Challenge + Response 后,就可以进行重放攻击。


9. 嵌入式工程应用场景

9.1 耗材认证

主机需要判断耗材是否为合法耗材。

典型做法是:

  1. 主机读取耗材 SN;
  2. 主机生成 Challenge;
  3. 耗材安全芯片使用内部密钥计算 MAC;
  4. 主机验证 MAC;
  5. 验证通过后,再检查批次、有效期、使用次数等授权信息。

这里 SN 只是身份识别,MAC 才是身份认证。


9.2 传感器或探头认证

主机需要判断外接传感器、探头或模块是否可信。

可以通过 MAC 认证确认:

  • 探头是否为合法探头;
  • 探头是否持有正确密钥;
  • 探头是否被简单克隆;
  • 探头返回的数据是否可信。

9.3 通信报文完整性保护

对于控制命令、参数设置、状态上报等报文,可以在协议尾部增加 MAC 字段。

接收方验证 MAC 后再执行命令。

这样可以防止攻击者修改命令内容,例如把:

复制代码
set_speed = 1000

篡改为:

复制代码
set_speed = 10000

如果没有正确密钥,攻击者无法重新计算合法 MAC。


9.4 固件升级过程保护

固件升级中,MAC 可以用于保护升级过程中的数据块完整性。

不过,对于正式固件发布验证,更推荐使用数字签名。

原因是:

  • MAC 使用共享密钥;
  • 设备端也要保存同一个验证密钥;
  • 如果设备端密钥泄露,攻击者可能伪造升级包;
  • 数字签名只需要设备端保存公钥,更适合固件发布场景。

因此,MAC 可以用于传输过程保护,但固件可信来源验证通常应使用数字签名。


10. 工程注意事项

MAC 的安全性不仅取决于算法本身,还取决于工程实现。

10.1 密钥不能明文放在普通 Flash 中

MAC 安全依赖密钥安全。

如果密钥直接明文放在 MCU 普通 Flash 中,一旦攻击者通过调试口、烧录口、固件读取或漏洞提取密钥,就可以伪造合法 MAC。

更安全的做法包括:

  • 使用加密芯片;
  • 使用 MCU 安全存储区域;
  • 启用读保护;
  • 关闭量产设备调试口;
  • 密钥分散存储;
  • 产线安全烧录;
  • 避免密钥出现在日志和调试输出中。

10.2 Challenge 不能固定或可预测

挑战响应中,Challenge 必须具备随机性或不可预测性。

不推荐的做法包括:

  • Challenge 固定为常量;
  • Challenge 使用简单递增但无保护的计数;
  • Challenge 由低精度时间直接生成;
  • Challenge 重复使用;
  • Challenge 可以被攻击者提前预测。

更好的做法是使用硬件随机数、真随机数源,或安全芯片提供的随机数能力。


10.3 MAC 比对应避免早退出

普通字符串比较函数通常在发现第一个不同字节时就返回。

这会导致不同错误位置产生不同执行时间。

在高安全需求场景下,攻击者可能通过时间差分析逐步推测正确 MAC。

因此,MAC 比对应使用常量时间比较,避免早退出造成时序泄露。

示例思想如下:

复制代码
int secure_compare(const uint8_t *a, const uint8_t *b, size_t len)
{
    uint8_t diff = 0;

    for (size_t i = 0; i < len; i++)
    {
        diff |= a[i] ^ b[i];
    }

    return diff == 0;
}

这段代码的思想是:无论前面字节是否相等,都遍历完整 MAC 长度。


10.4 不要自行设计 MAC 算法

不要自己设计类似下面的算法:

复制代码
MAC = Hash(Key + Message)
MAC = Hash(Message + Key)
MAC = CRC(Key + Message)
MAC = AES_Encrypt(Hash(Message))

这些看起来像安全方案,但可能存在结构性风险。

工程上应优先使用成熟算法,例如:

  • HMAC-SHA256;
  • AES-CMAC;
  • 安全芯片厂商提供的认证接口;
  • 经过验证的密码库实现。

10.5 共享密钥要考虑设备级隔离

如果所有设备使用同一个共享密钥,一台设备密钥泄露后,可能影响整批设备。

更好的做法是:

  • 每台设备使用唯一密钥;
  • 按批次派生密钥;
  • 使用根密钥派生设备密钥;
  • 密钥与设备 SN、证书或唯一 ID 绑定;
  • 产线烧录后不再明文导出密钥。

这样可以降低单台设备泄露后的影响范围。


11. MAC、签名与加密的区别

机制 解决的问题 密钥特点 典型用途
MAC / HMAC 完整性 + 来源验证 双方共享密钥 挑战响应、报文认证
数字签名 完整性 + 发布方认证 私钥签名、公钥验证 固件签名、证书链
加密 内容保密 对称或非对称密钥 保护敏感数据内容

简单理解:

MAC 证明:对方知道同一个密钥;

签名证明:数据来自私钥持有者;

加密证明不了来源,只是隐藏内容。


12. 工程总结

MAC 是嵌入式认证中非常实用的工具。

它适合用于:

  • 低成本设备认证;
  • 低资源消息完整性保护;
  • 低延迟挑战响应;
  • 耗材、探头、传感器认证;
  • 主机与外设之间的报文认证。

MAC 的核心价值在于:

它把 Hash 的完整性能力,和密钥带来的身份可信能力结合在一起。

但是,MAC 的安全性强依赖于:

  • 密钥是否安全;
  • 随机数是否可靠;
  • 算法是否标准;
  • 实现是否正确;
  • 设备密钥是否具备隔离性。

因此,工程上应优先使用 HMAC-SHA256、AES-CMAC 或安全芯片提供的认证方案,不建议自行设计变体。

相关推荐
cc.ChenLy3 小时前
算法从入门到精通实战指南
算法
Jerry10 小时前
LeetCode 160. 相交链表
算法
Jerry10 小时前
LeetCode 19. 删除链表的倒数第 N 个结点
算法
金銀銅鐵10 小时前
费马小定理
python·数学·算法
技术不好的崎鸣同学11 小时前
[ACTF2020 新生赛]Exec 思路及解法
算法·安全·web安全
Full Stack Developme13 小时前
Java LRU 与 LFU 算法及应用
java·开发语言·算法
Jerry14 小时前
LeetCode 707. 设计链表
算法
C语言小火车14 小时前
C++ 堆排序深度精讲:基于完全二叉树的选择排序进化,最坏情况 O(n log n) 的稳定王者
开发语言·c++·算法·排序算法·堆排序