3、数字签名原理:签名生成流程、签名验证流程、哈希函数的作用

数字签名,说白了就是固件更新链上的「防伪印章」。你想想看,设备怎么知道收到的固件真的是厂商发的,而不是中间人塞了个恶意版本?靠的就是这套签名机制。我做了这么多年固件安全,可以负责任地告诉你——没有数字签名的固件更新,跟裸奔没什么区别。

3.1 签名生成流程:私钥的「封印」

签名生成发生在厂商侧,用的是私钥。私钥必须绝对保密,这是整个信任模型的根基。我在项目中见过有人把私钥硬编码在编译脚本里……嗯,那场面,后来整个产品线都得召回。

签名生成的核心步骤,我习惯拆成三步:

  1. 计算固件哈希:对固件二进制做哈希运算,得到一个固定长度的摘要。
  2. 用私钥加密哈希:把哈希值用厂商的私钥进行非对称加密,生成签名。
  3. 打包分发:将固件 + 签名 + 公钥证书打包在一起,推送到设备端。

关键点:签名不是加密整个固件,而是加密固件的哈希值。为什么?因为非对称加密很慢,固件动不动几十兆,加密整个固件不现实。哈希函数把任意长度的固件「压缩」成固定长度(比如256位),再加密这个短摘要,效率就上来了。

这里有个细节——签名 ≠ 加密。加密是为了保密,签名是为了防篡改和防抵赖。我刚开始做固件时也混淆过,后来踩了坑才明白:签名用的是私钥,验证用的是公钥;加密正好反过来。

3.2 签名验证流程:公钥的「解封」

设备收到固件后,需要验证签名是否有效。验证流程同样三步:

  1. 提取签名和公钥:从固件包中取出签名和公钥证书。
  2. 用公钥解密签名:公钥解密签名,得到「原始哈希值」。
  3. 计算本地哈希并比对:对收到的固件重新计算哈希,与解密出的哈希比对。一致则通过,不一致则拒绝。

避坑指南:我曾经遇到一个案例,设备验证签名时只检查了签名格式,没检查公钥来源。攻击者直接替换了公钥,用自己的私钥签名恶意固件,设备照单全收。所以记住——公钥必须来自可信根,不能从固件包里随便拿。

验证流程的数学基础是:私钥加密的东西,只有对应的公钥能解开。如果公钥能正确解密签名,说明这个签名一定是用对应的私钥生成的。这就是「非对称」的精髓。

3.3 哈希函数的作用:数字签名的「减负器」

哈希函数在签名链里扮演什么角色?我总结了三句话:

  • 压缩:把任意大小的固件变成固定长度的摘要。
  • 防碰撞:找不到两个不同的固件有相同的哈希值。
  • 单向性:从哈希值无法反推出原始固件。

没有哈希函数,签名就得直接加密整个固件。你想想看,一个32MB的固件,用RSA-2048加密,一次签名要加密256次(每次加密256字节),性能直接崩了。哈希函数把32MB变成32字节,一次加密搞定。

特性 有哈希函数 无哈希函数
签名对象 32字节哈希值 整个固件(可能几十MB)
签名速度 毫秒级 分钟级(甚至更久)
安全性 哈希碰撞概率极低 直接暴露固件内容
存储开销 签名固定大小(如256字节) 签名与固件等大

注意:哈希函数的选择很重要。SHA-1已经被证明有碰撞攻击,别再用了。我建议至少用SHA-256,如果对安全性要求极高,可以考虑SHA-3或BLAKE2。曾经有厂商用MD5做固件签名哈希,结果被碰撞攻击伪造了签名,整个产品线都得打补丁。

3.4 完整签名流程框架图

下面这张图把签名生成和验证的完整流程串起来了。我建议你多看几遍,把每一步的输入输出搞清楚。

数字签名完整流程 签名生成(厂商侧) 固件二进制 哈希函数 哈希值 私钥 签名(加密后的哈希) 传输到设备 签名验证(设备侧) 收到的固件 哈希函数 本地哈希值 公钥 解密后的原始哈希值 比对:本地哈希 vs 原始哈希 一致 → 通过 不一致 → 拒绝 核心逻辑 私钥签名 → 公钥验证 → 哈希比对 → 决定固件是否可信

3.5 常见误区与避坑

做固件签名验证,有几个坑我几乎年年都能遇到:

  • 签名和加密混为一谈:签名用私钥,加密用公钥,方向别搞反了。
  • 哈希函数选错:MD5、SHA-1已经不安全,别再用。
  • 公钥来源不校验:公钥必须来自硬件信任根或证书链,不能从固件包直接拿。
  • 签名验证跳过:有些设备只在首次启动时验证签名,后续更新不验证——这是致命漏洞。

个人经验:我习惯在设备启动时做两次验证——一次在Bootloader阶段,一次在OS加载固件时。双重验证虽然多花几毫秒,但能挡住大部分攻击。曾经有个客户只做了Bootloader验证,结果攻击者绕过了Bootloader直接写Flash,固件就被篡改了。

数字签名不是银弹,但它是固件更新安全的第一道防线。把签名生成和验证流程搞扎实,哈希函数选对,公钥管好,你的固件更新链就稳了一大半。