很多设备安全设计里,会出现一句很危险的话:“我们对数据做了 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。