3. 数字签名原理:签名生成与验签流程

数字签名,说白了就是给固件盖一个「防伪章」。你想想看,固件从开发环境一路烧录到设备上,中间经过了多少双手?编译服务器、存储服务器、下载工具、OTA 通道……任何一个环节被动手脚,设备就可能变成别人的傀儡。

我早年做 IoT 网关时,就吃过这个亏。当时产品已经量产了,突然发现某批次设备在启动时加载了异常代码。查到最后,是生产线的烧录工具被植入了恶意脚本。从那以后,我对数字签名这件事就特别较真。

3.1 核心概念:公钥与私钥

数字签名依赖一对密钥:私钥和公钥。它们是一起生成的,数学上互相关联,但私钥不能从公钥推导出来。

密钥 谁持有 用途 保密要求
私钥 签名者(固件开发者/发布者) 生成签名 绝对保密,泄露即失控
公钥 验签者(设备/用户) 验证签名 公开,但需防篡改
⚠ 我曾经踩过的坑:私钥不能直接放在编译脚本里,更不能上传到 Git 仓库。有一次同事把私钥写死在 Makefile 里,结果整个仓库被 fork 出去……嗯,后面的事你懂的。

3.2 签名生成流程

签名不是对固件全文加密,而是对固件的哈希值做签名。这样做有两个好处:一是速度快,二是签名长度固定。

  1. 计算固件哈希:用 SHA-256 或 SHA-384 算出固件的摘要(digest)。
  2. 用私钥加密哈希:对摘要执行 RSA 或 ECDSA 签名算法,输出签名数据。
  3. 打包:将原始固件 + 签名 + 证书链打包成一个发布文件。

我习惯在 CI/CD 流水线里加一个签名步骤。每次构建完成后,自动调用签名工具,把私钥从硬件安全模块(HSM)里读出来,而不是从文件系统里读。

# 伪代码示例:签名生成
digest = SHA256(firmware.bin)
signature = RSA_SIGN(digest, private_key)
package = firmware.bin + signature + certificate_chain
💡 个人建议:签名时最好带上时间戳。这样即使私钥后来泄露,也能通过时间窗口判断哪些固件是可信的。

3.3 验签流程

验签是签名的逆过程,但不需要私钥。设备端只需要公钥就能完成验证。

  1. 提取签名和固件:从包中分离出原始固件和签名数据。
  2. 计算固件哈希:用同样的哈希算法算出摘要。
  3. 用公钥解密签名:对签名执行 RSA 或 ECDSA 验签算法,得到原始摘要。
  4. 比对两个摘要:如果一致,签名有效;否则拒绝加载。
# 伪代码示例:验签
digest = SHA256(firmware.bin)
original_digest = RSA_VERIFY(signature, public_key)
if digest == original_digest:
    print("签名验证通过")
else:
    print("固件被篡改,拒绝启动")
🔑 关键点:验签失败时,设备必须进入安全恢复模式,不能回退到旧版本或执行未经验证的代码。这是防篡改的最后一道防线。

3.4 数学基础:为什么私钥签名、公钥验签能工作?

这里我不打算堆公式,但你需要理解一个核心思想:单向陷门函数。

以 RSA 为例:

  • 私钥签名 = 用私钥指数 d 对哈希做模幂运算:signature = hash^d mod n
  • 公钥验签 = 用公钥指数 e 对签名做模幂运算:hash' = signature^e mod n
  • 因为 d * e ≡ 1 (mod φ(n)),所以 hash' = hash

说白了,就是私钥和公钥在数学上互为「逆运算」。只有私钥能生成签名,而公钥只能验证,不能伪造。

ECDSA 的原理类似,但基于椭圆曲线上的点乘运算。我个人更推荐 ECDSA,因为同样的安全强度下,密钥更短、签名更小,对嵌入式设备更友好。

⚠ 注意:不要使用 MD5 或 SHA-1 做哈希。我在 2018 年审计过一个项目,他们还在用 SHA-1 签名固件。结果我用一个现成的碰撞工具,五分钟就伪造了一个合法签名。嗯,那个项目后来全部返工了。

3.5 签名与验签流程图

下面这张图把整个流程串起来了。你可以把它当作固件签名验证的「全景地图」。

数字签名生成与验签流程 签名生成(开发者侧) ① 计算固件哈希 SHA-256 ② 私钥签名(RSA/ECDSA) ③ 打包固件 + 签名 + 证书链 验签(设备侧) ① 提取固件和签名 ② 计算固件哈希 SHA-256 ③ 公钥验签,比对哈希 ✅ 通过 → 启动 ❌ 失败 → 拒绝 ⚠ 关键原则 私钥永远不离开签名服务器 / HSM;公钥必须安全存储在设备一次性写入区域(OTP)

3.6 常见误区与避坑指南

  • 误区一:签名 = 加密。签名不隐藏固件内容,只保证完整性和来源可信。要加密,请用 AES 或 ChaCha20。
  • 误区二:公钥随便存。公钥如果被替换,攻击者可以用自己的私钥签名恶意固件。公钥必须烧录在设备的 OTP 区域,且不可改写。
  • 误区三:签名一次就够了。我见过一个项目,固件签名只在发布时做一次,但 OTA 下载过程中没有二次校验。结果中间人攻击直接把签名替换了。
💡 我的经验:在设备启动的每个阶段都做签名校验——BootROM 校验 Bootloader,Bootloader 校验固件头部,固件头部校验固件主体。层层验证,才能防住针对不同阶段的攻击。

好了,数字签名的原理就讲到这里。你理解了私钥签名、公钥验签的流程和数学基础,后面讲证书链时就能更顺畅地跟上。记住一句话:私钥是命根子,公钥是守门员。