跳转到主内容哈希、MAC 和签名 | 物联网民工

哈希、MAC 和签名

很多设备安全设计里,会出现一句很危险的话:“我们对数据做了 SHA-256,所以是安全的。”

这句话通常不够。SHA-256 是哈希函数,它能把数据变成固定长度摘要,但它不知道是谁算的,也不能阻止攻击者把数据改了以后重新算一个摘要。

哈希、MAC 和签名都会输出一段“看起来像校验值”的东西,但它们解决的问题不同:

Hash:有没有变过,前提是摘要本身可信
MAC:是不是持有同一把共享密钥的人生成的
Signature:是不是某个私钥持有者生成的,任何人可用公钥验证

把这三者混在一起,会直接影响固件验签、设备认证、消息防篡改和日志取证。

哈希是数据指纹,不是身份认证

哈希函数把任意长度输入变成固定长度摘要:

message -> SHA-256 -> digest

它适合做:

  • 文件指纹
  • 固件完整性摘要
  • 去重和索引
  • Merkle tree
  • 签名前的摘要
  • 下载后比对公开 checksum

哈希能回答的问题是:“这份数据和摘要对应的数据是否一致?”

但它不能回答:

  • 摘要是谁给的
  • 摘要有没有被一起篡改
  • 数据是不是来自可信设备
  • 发送者是不是持有某个密钥

如果攻击者能同时修改文件和哈希值,单独哈希没有防护效果。

firmware.bin
firmware.bin.sha256

这两个文件如果来自同一个不可信通道,攻击者可以替换固件后重新生成 .sha256。设备验证通过,但验证的是攻击者给的新摘要。

所以哈希必须和可信来源结合:可信发布页、签名、证书、只读存储、Secure Boot 元数据,或者其他可信通道。

MAC 是共享密钥下的消息认证

MAC,全称 Message Authentication Code,用共享密钥生成认证标签:

message + shared key -> HMAC/CMAC -> tag

验证方也持有同一把共享密钥:

message + shared key -> recompute tag -> compare

MAC 解决的是:这段消息是否由持有同一把密钥的一方生成,并且中途没有被改。

常见算法包括:

  • HMAC-SHA256
  • AES-CMAC
  • Poly1305

MAC 适合:

  • 设备和服务器之间的共享密钥认证
  • 内部消息完整性保护
  • 协议报文认证
  • token 或配置的防篡改
  • 已有安全信道内的轻量认证

MAC 的关键边界是:验证方也能生成同样的 tag。因为双方共享同一把密钥,所以 MAC 不能证明“到底是哪一方生成的”。它只能证明“某个持有这把密钥的人生成的”。

这对设备认证足够常见,但对公开验签、第三方审计和不可抵赖不够。

签名是私钥生成、公钥验证

数字签名使用非对称密钥:

message + private key -> signature
message + public key + signature -> verify

私钥只有签名者持有,公钥可以分发给验证者。验证者能确认签名由对应私钥生成,但不能用公钥伪造签名。

常见算法包括:

  • ECDSA P-256
  • Ed25519
  • RSA-PSS

签名适合:

  • 固件签名
  • Secure Boot
  • OTA 包验证
  • 设备证书链
  • 日志或审计记录
  • 第三方可验证的授权声明

签名和 MAC 最大差别是信任分发方式。

MAC 要求验证方持有共享密钥;签名只要求验证方持有公钥。这让签名更适合“大量设备验证同一个发布方”的场景。

例如固件验签:设备里只需要放厂商公钥或证书链,发布系统用私钥签名固件。设备能验签,但不能伪造厂商签名。

如果用 MAC 做同样的事,每台设备都要持有能生成有效 tag 的密钥。一旦设备被攻破,攻击者可能拿到能伪造固件认证的材料。

三者解决的不是同一件事

可以用这个表做第一判断:

Hash
    输入:数据
    密钥:无
    验证者:任何人
    证明:数据和摘要匹配
    风险:摘要本身如果不可信,就没有认证意义

MAC
    输入:数据 + 共享密钥
    密钥:双方共享
    验证者:持有同一密钥的人
    证明:消息来自某个密钥持有者,且未被改
    风险:验证者也能伪造,密钥泄露影响双方

Signature
    输入:数据 + 私钥
    密钥:私钥签名,公钥验证
    验证者:持有公钥的人
    证明:消息由对应私钥签名,且未被改
    风险:私钥保护和公钥信任链是核心

一句话:

Hash 解决指纹
MAC 解决共享密钥认证
Signature 解决公开可验证的身份绑定

为什么不能把密钥直接拼进哈希

有些实现会写:

SHA256(key || message)

或者:

SHA256(message || key)

这不是推荐的 MAC 设计。

原因是普通哈希函数不是按“消息认证码”语义设计的。某些构造会遇到长度扩展攻击、边界歧义、拼接歧义和协议演进问题。

HMAC 的价值就是把哈希函数包装成安全的消息认证码构造:

HMAC(key, message)

工程上不要自己拼 key 和 message。需要 MAC 就用 HMAC、CMAC 或协议指定的认证算法。

固件完整性为什么通常需要签名

固件安全里常见三个层次:

第一,哈希。它能确认固件内容和某个摘要一致,但摘要必须可信。

第二,MAC。它能确认固件由持有共享密钥的一方认证过,但验证设备也持有同样密钥,密钥泄露风险扩散。

第三,签名。发布方私钥签名,设备公钥验签。设备能验证但不能伪造发布方签名。

所以 Secure Boot 和 OTA 验签通常使用数字签名,而不是只放一个 hash,也不是让所有设备共享一个 MAC 密钥。

哈希仍然会参与其中,但通常作为签名输入的一部分:

firmware -> hash -> sign hash
device -> hash firmware -> verify signature

签名保护的是“这个摘要确实由可信私钥授权”,不是让哈希函数变成身份认证。

消息认证为什么常用 MAC

设备和服务器之间如果已经共享密钥,MAC 很适合做消息认证。

例如设备上报:

device_id
timestamp
counter
payload
tag = HMAC(device_key, fields)

服务器验证 tag 后,能确认这条消息来自持有 device_key 的设备,并且字段没有被改。

但要注意几个边界:

  • MAC 不加密 payload,机密性要靠加密或 AEAD
  • MAC 要覆盖协议里所有关键字段
  • timestamp/counter/nonce 要防重放
  • tag 比较要避免时序泄露
  • 共享密钥泄露后,攻击者可以伪造消息

MAC 是认证,不是加密,也不是自动防重放。

证书链本质上是签名链

设备证书、服务器证书、CA 证书,本质上都依赖签名。

一张设备证书大致表达:

CA 私钥签名:这个设备公钥属于这个设备身份

验证方用 CA 公钥验证证书签名,再用设备证书里的设备公钥验证设备握手签名。

这和 MAC 的模型完全不同。服务器不需要知道设备私钥,也不需要和所有设备共享同一把认证密钥。它只需要信任 CA 链,并验证设备证明自己持有对应私钥。

这也是证书体系能扩展到大量设备的原因。

排查时先问三个问题

看到一个“校验值”“摘要”“签名”“token”时,先问:

它有没有密钥?
-> 没有:多半是 hash

验证方是否也能生成同样结果?
-> 能:多半是 MAC

是否私钥生成、公钥验证?
-> 是:签名

再问:

  • 这个值保护的是完整性、认证、还是身份责任
  • 密钥在哪里生成和保存
  • 验证方是否应该具备伪造能力
  • 是否需要第三方验证
  • 是否需要防重放
  • 是否覆盖了所有关键字段
  • hash 或公钥本身是否来自可信来源

很多安全设计错误不是算法选错,而是把一个机制的承诺用到了另一个场景。

真正要记住的边界

哈希、MAC 和签名都和“数据没被改”有关,但承诺层级不同。

哈希给数据做指纹,前提是指纹可信。MAC 用共享密钥证明消息来自某个密钥持有者。签名用私钥和公钥建立公开可验证的身份绑定。

设备安全里,下载校验、消息认证、固件验签、证书链和审计记录分别需要不同机制。先分清要解决的是完整性、共享密钥认证,还是公开验签,再选择 hash、MAC 或 signature。