第1章:升级包制作与签名

各位同学,今天咱们聊聊升级包制作与签名。这玩意儿,说白了就是给系统升级包上把锁,再加个身份证。我在嵌入式领域摸爬滚打这么多年,见过太多因为升级包没做好签名,导致设备变砖的惨案。嗯,咱们一步步来拆解。

1.1 升级包结构设计

先说说升级包长什么样。我个人习惯把升级包设计成三层结构:头部、数据区、尾部签名区。你想想看,这就像寄快递——头部是快递单,数据区是包裹里的东西,尾部签名就是收件人的签字确认。

升级包结构示意:

+------------------+
|  包头 (Header)   |  ← 元数据:版本号、包大小、校验算法等
+------------------+
|  数据区 (Data)   |  ← 实际升级内容:rootfs镜像、内核、dtb等
+------------------+
|  签名区 (Sign)   |  ← 数字签名:用于验证包完整性
+------------------+

我在项目中遇到过一种情况:有人把升级包做得特别复杂,嵌套了好几层压缩。结果解压时内存不够,直接崩了。所以我的建议是:结构尽量扁平,别搞花里胡哨的。

包头里必须包含的信息有:

  • 魔数:用于快速识别是不是升级包,比如 0x55475200
  • 版本号:主版本.次版本.修订号,比如 2.1.3
  • 包体大小:数据区的实际长度
  • 哈希算法标识:用了哪种哈希,比如 SHA256
  • 目标设备ID:防止刷错设备

小技巧:包头里加个「兼容性标志位」。我曾经吃过亏,新版本升级包刷到旧硬件上,驱动不兼容直接黑屏。加了这个标志位,升级前就能做检查。

1.2 文件校验与哈希算法

文件校验,说白了就是给升级包算个「指纹」。你想想看,如果升级包在传输过程中被篡改了一个字节,那刷进去可能就是灾难。哈希算法就是干这个的。

常用的哈希算法有这些:

算法 输出长度 安全性 我的建议
MD5 128位 低(已破解) 别用了,真的
SHA1 160位 中(有碰撞风险) 老项目可能还在用
SHA256 256位 高 推荐,目前够用
SHA512 512位 极高 资源够的话可以用

我个人习惯用 SHA256。为什么?因为它在安全性和计算速度之间取得了很好的平衡。我在一个物联网项目里试过 SHA512,结果低端 MCU 算一次要好几秒,用户体验极差。

校验流程其实很简单:

  1. 升级前,计算整个数据区的哈希值
  2. 跟包头里记录的哈希值对比
  3. 一致就继续,不一致就拒绝升级

注意:千万别只校验文件头!我曾经见过一个案例,攻击者把恶意代码塞在数据区尾部,只校验了前 1MB。结果...嗯,设备被远程控制了。一定要校验整个数据区。

1.3 数字签名与验签流程

哈希校验只能防「意外损坏」,防不了「恶意篡改」。为什么?因为攻击者可以同时修改数据和哈希值。这时候就需要数字签名了。

数字签名的核心是非对称加密。简单说就是:

  • 私钥:放在服务器上,用来签名
  • 公钥:烧录在设备里,用来验签

流程是这样的:

数字签名与验签流程 服务器端(签名) 设备端(验签) ① 计算数据区哈希值 H ② 用私钥加密 H → 签名 S ③ 打包:数据 + 签名 S 传输 ④ 用公钥解密签名 → 得到 H' ⑤ 计算数据区哈希值 H'' ⑥ 比较 H' == H'' ?

你看这个流程,核心就是「私钥签名,公钥验签」。私钥一旦泄露,整个安全体系就崩了。所以私钥一定要保管好,最好用硬件安全模块(HSM)来存。

实际项目中的验签代码片段:

// 伪代码,实际用 OpenSSL 或 mbedTLS
int verify_package(uint8_t *data, uint32_t len, uint8_t *signature) {
    // 1. 计算数据哈希
    uint8_t hash[32];
    sha256(data, len, hash);
    
    // 2. 用公钥解密签名
    uint8_t decrypted_hash[32];
    rsa_decrypt(signature, decrypted_hash, public_key);
    
    // 3. 比较
    if (memcmp(hash, decrypted_hash, 32) == 0) {
        return OK;  // 验签通过
    } else {
        return FAIL; // 包被篡改
    }
}

避坑指南:我曾经在一个项目里,公钥是硬编码在固件里的。结果有次升级,公钥位置被不小心改动了,所有升级包都验签失败。后来我改成把公钥放在单独的存储分区,升级时先校验公钥分区自身的完整性。嗯,这招挺管用。

还有个细节:签名算法选 RSA 还是 ECC?我个人倾向 ECC。为什么?因为 ECC 的密钥更短,计算更快。在资源受限的嵌入式设备上,ECC 256 位的安全性相当于 RSA 3072 位,但计算量小得多。

我的经验:ECC 的签名验证在 Cortex-M4 上大概需要 50ms,RSA 要 200ms 以上。如果设备每次启动都要验签,这个差距就很明显了。

最后说一句,签名不是万能的。它只能保证「包是官方发的」和「包没被篡改」。但如果私钥泄露了,或者设备里的公钥被替换了,那一切白搭。所以密钥管理才是真正的核心。这个咱们后面章节再细聊。


可可精品资料库