3、固件签名与验证:非对称加密基础(RSA/ECC),哈希算法(SHA-256)的应用,固件签名的生成与验证流程。
说到固件安全,签名验证是绕不开的一环。我刚开始做电机控制时,总觉得这玩意儿是「大厂才需要考虑的事」。直到有一次,客户现场反馈说电机启动时偶尔会「抽风」,查了三天才发现是有人用烧录器刷了个来路不明的固件进去。嗯,从那以后,我再也不敢跳过签名验证了。
说白了,固件签名就是给你的固件打上一个「防伪标签」。别人想伪造?门儿都没有。今天我们就来拆解一下,这个标签到底是怎么打上去的,又是怎么被验证的。
3.1 非对称加密:公钥与私钥的「锁与钥匙」
非对称加密,你可以想象成一把特殊的锁和两把钥匙。一把叫私钥,你藏好,谁都不给看。另一把叫公钥,你可以随便发给别人,甚至贴在墙上。
有意思的地方在于:用私钥加密的东西,只有公钥能解开;用公钥加密的东西,只有私钥能解开。这个特性,恰好可以用来做签名。
目前主流的非对称加密算法有两种:RSA 和 ECC(椭圆曲线密码学)。我个人的习惯是,在资源受限的 MCU 上优先选 ECC,因为它密钥更短,算力消耗更小。
| 对比项 | RSA | ECC |
|---|---|---|
| 密钥长度(安全等价) | 2048 位 | 256 位 |
| 签名速度 | 较慢 | 较快 |
| 验证速度 | 较快 | 较慢 |
| 存储占用 | 大 | 小 |
| 典型应用 | PC/服务器端 | 嵌入式/IoT 设备 |
3.2 哈希算法:给固件「算个指纹」
你想想看,固件可能几百 KB 甚至几 MB,直接用非对称加密去签名整个固件,计算量太大了。这时候就需要哈希算法出场。
哈希算法,说白了就是给固件算一个「数字指纹」。不管你的固件有多大,算出来的哈希值都是固定长度。而且,只要固件里有一个 bit 变了,哈希值就会变得面目全非。
我们这里用的是 SHA-256,它输出 256 位(32 字节)的哈希值。这个长度在安全性和效率之间取得了很好的平衡。
核心逻辑:
- 对固件做 SHA-256 哈希,得到 digest(摘要)
- 用私钥对 digest 加密,得到 signature(签名)
- 验证时,用公钥解密 signature,得到 digest1
- 再对固件做 SHA-256,得到 digest2
- 如果 digest1 == digest2,固件就是可信的
3.3 签名生成流程:在 PC 端完成
签名生成通常是在你的开发 PC 上完成的,MCU 只负责验证。流程大概是这样的:
- 编译固件:生成 .bin 或 .hex 文件
- 计算哈希:对固件文件做 SHA-256
- 私钥签名:用 RSA 或 ECC 私钥加密哈希值
- 打包:将原始固件 + 签名 + 公钥(可选)打包成一个文件
这里有个细节要注意:打包的时候,签名通常放在固件的末尾或者头部。我习惯放在头部,这样 Bootloader 读取起来更方便。
# 伪代码示例:签名生成
firmware_data = read_file("motor_ctrl.bin")
digest = SHA256(firmware_data)
signature = ECC_sign(private_key, digest)
packed = firmware_data + signature + public_key
write_file("motor_ctrl_signed.bin", packed)
3.4 签名验证流程:在 MCU 上完成
MCU 端的验证,是电机安全启动的核心。每次上电,Bootloader 都会执行以下步骤:
- 读取打包固件:从 Flash 中读取固件数据、签名和公钥
- 计算哈希:对固件数据做 SHA-256
- 公钥解密:用预置的公钥解密签名,得到原始哈希
- 比对:如果两个哈希一致,跳转到固件入口;否则,进入错误处理
// 伪代码示例:MCU 端验证
uint8_t* fw_data = (uint8_t*)FLASH_APP_ADDR;
uint32_t fw_len = get_firmware_length();
uint8_t* signature = fw_data + fw_len;
uint8_t* public_key = signature + SIGNATURE_LEN;
uint8_t digest1[32];
SHA256(fw_data, fw_len, digest1);
uint8_t digest2[32];
ECC_verify(public_key, signature, digest2);
if (memcmp(digest1, digest2, 32) == 0) {
jump_to_app();
} else {
error_handler(); // 固件被篡改,停止启动
}
3.5 整体流程可视化
为了让你看得更清楚,我画了一张流程图。它把签名生成和验证两条线放在一起对比,一目了然。
3.6 避坑指南:我踩过的几个雷
- 公钥存储位置:前面说过了,别放固件里。OTP 或者 Bootloader 硬编码是最安全的。
- 签名长度固定:ECC-256 的签名是 64 字节,RSA-2048 是 256 字节。解析打包固件时,别把长度算错了。
- 哈希计算范围:SHA-256 只对固件代码段计算,不要把签名本身也算进去。否则你永远验证不过。
- Bootloader 自更新:如果 Bootloader 本身也需要更新,那它的签名验证逻辑谁来保证?这是个鸡生蛋蛋生鸡的问题。我一般把 Bootloader 放在写保护的 Flash 区域,永远不更新。