MAC 是 Message Authentication Code ,即消息认证码。
它的核心作用是同时验证:
- 消息完整性:消息有没有被篡改;
- 消息来源:消息是否来自持有正确密钥的一方。
相比单独 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 的基本流程如下:
- 发送方准备消息内容;
- 发送方使用共享密钥和消息内容计算 MAC;
- 发送方把消息和 MAC 一起发送给接收方;
- 接收方使用同一密钥重新计算 MAC;
- 接收方把自己计算出的 MAC 与收到的 MAC 进行比较;
- 如果一致,说明验证通过;
- 如果不一致,说明消息可能被篡改,或者发送方不可信。
可以简化理解为:
发送方:
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 常用于挑战响应认证。
挑战响应要解决的问题是:
如何证明设备拥有正确密钥,但又不把密钥发出来?
典型流程如下:
- 主机生成随机数 Challenge;
- 主机把 Challenge 发送给设备;
- 设备使用内部密钥对 Challenge 计算 MAC;
- 设备返回 Response;
- 主机使用相同密钥计算期望 Response;
- 主机比较设备返回值和本地计算值;
- 如果一致,认证通过。
流程可以表示为:
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 耗材认证
主机需要判断耗材是否为合法耗材。
典型做法是:
- 主机读取耗材 SN;
- 主机生成 Challenge;
- 耗材安全芯片使用内部密钥计算 MAC;
- 主机验证 MAC;
- 验证通过后,再检查批次、有效期、使用次数等授权信息。
这里 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 或安全芯片提供的认证方案,不建议自行设计变体。