4、签名与验签机制:非对称加密基础(RSA/ECC)、哈希算法(SHA-256)、数字签名生成与验证流程
各位同学,咱们今天聊点硬核的——签名与验签。说实话,这是车载MCU安全启动里最核心的一环。你想想看,如果ECU启动时没法确认固件是谁写的、有没有被篡改,那整个车就跟没锁门一样危险。
我个人习惯把签名验签比作「电子版的亲笔签名+防伪封条」。你签了名,别人能认出是你写的;封条没破,说明内容没被改过。在嵌入式世界里,我们靠的是数学,不是纸和笔。
4.1 非对称加密:公钥与私钥的「钥匙对」
非对称加密,说白了就是两把钥匙。一把公开(公钥),一把私藏(私钥)。公钥可以满大街发,私钥必须锁在保险柜里。
我在项目中遇到过不少新手问:「公钥都公开了,那加密还有什么意义?」其实关键在这里:用私钥加密的东西,只有公钥能解开;用公钥加密的东西,只有私钥能解开。这个单向性,就是安全的基石。
车载领域常用的非对称算法有两种:
| 算法 | 密钥长度 | 签名速度 | 验签速度 | 典型应用 |
|---|---|---|---|---|
| RSA | 2048 / 4096 bit | 较慢 | 较快 | 传统车载ECU、T-Box |
| ECC(椭圆曲线) | 256 bit(等效RSA 3072) | 较快 | 较快 | 新一代MCU、域控制器 |
嗯,这里要注意:MCU资源有限,ECC在同等安全强度下密钥更短、计算更快。我建议新项目优先考虑ECC,尤其是NIST P-256曲线,硬件加速支持也最成熟。
核心要点:签名用的是私钥,验签用的是公钥。私钥一旦泄露,整个信任链就崩了。我曾经见过某供应商把私钥硬编码在源码里提交到Git仓库……嗯,那场面,不说了。
4.2 哈希算法:SHA-256 的「数字指纹」
签名之前,必须先做哈希。为什么?因为非对称加密算得慢,你不可能把整个固件(动不动几MB)直接拿去签名。哈希的作用就是:把任意长度的数据,压缩成一个固定长度的摘要(256位)。
SHA-256的特点:
- 单向性:从摘要反推原文,理论上不可能
- 抗碰撞性:几乎找不到两个不同文件有相同哈希值
- 雪崩效应:改一个bit,哈希值天翻地覆
我在调试一个OTA升级方案时,就遇到过哈希校验不通过的问题。查了半天,发现是Bootloader读Flash时多读了4个字节的填充数据。你想想看,就多了4个0xFF,哈希值完全变了。所以哈希计算的范围必须严格对齐,多一个字节都不行。
实战技巧:车载MCU通常内置硬件哈希加速器(如HSM模块)。别用软件算SHA-256,太慢了。我习惯直接调用硬件API,一次4KB数据块,效率能提升10倍以上。
4.3 数字签名生成流程:从固件到签名文件
好了,咱们把流程串起来。签名生成发生在开发环境(PC端或云端),不是在MCU上。流程如下:
- 计算固件哈希:对原始固件二进制做SHA-256,得到32字节摘要
- 填充摘要:按PKCS#1 v1.5或PSS标准填充,防止数学攻击
- 私钥签名:用RSA或ECC私钥对填充后的摘要做加密运算
- 生成签名文件:签名结果(256/512字节)附加到固件尾部,或单独存放
代码示例(伪代码,实际用OpenSSL或mbedTLS):
// 签名生成(PC端)
uint8_t firmware[FW_SIZE]; // 原始固件
uint8_t hash[32]; // SHA-256输出
uint8_t signature[256]; // RSA-2048签名
// 1. 计算哈希
SHA256(firmware, FW_SIZE, hash);
// 2. 私钥签名(使用HSM或软件密钥)
RSA_sign(private_key, hash, sizeof(hash), signature, &sig_len);
// 3. 将signature附加到固件尾部,或写入单独签名文件
注意:签名时使用的哈希算法必须和验签时一致。我曾经踩过坑——PC端用SHA-256签名,MCU端硬件加速器默认用SHA-1,结果验签死活过不了。嗯,这种低级错误,犯一次就够了。
4.4 数字签名验证流程:MCU端的「信任检查」
验签发生在MCU的Bootloader里。每次上电或OTA升级后,Bootloader必须验证固件的合法性。流程如下:
- 读取固件和签名:从Flash中加载固件数据,以及附加的签名值
- 计算固件哈希:对固件区域重新做SHA-256
- 公钥验签:用预置的公钥,验证签名是否匹配哈希
- 判断结果:验签通过→启动固件;验签失败→进入恢复模式
代码示例(MCU端,使用硬件加速):
// 验签(MCU Bootloader)
uint8_t firmware[FW_SIZE]; // Flash中的固件
uint8_t stored_sig[256]; // 固件尾部存储的签名
uint8_t hash[32]; // 重新计算的哈希
// 1. 计算固件哈希(调用硬件SHA引擎)
HSM_SHA256(firmware, FW_SIZE, hash);
// 2. 公钥验签(调用硬件RSA引擎)
if (HSM_RSA_verify(public_key, hash, sizeof(hash),
stored_sig, sig_len) == PASS) {
// 验签通过,跳转到固件入口
jump_to_app(firmware);
} else {
// 验签失败,进入安全恢复模式
enter_recovery_mode();
}
你可能会问:「公钥存在哪里?」嗯,这是个好问题。公钥通常烧录在OTP(一次性可编程)区域或eFuse里,一旦写入就无法修改。这样即使攻击者拿到了Flash内容,也换不了公钥。
4.5 避坑指南:我踩过的那些坑
做签名验签这么多年,有些坑真的是刻骨铭心。分享几个给大家:
- 公钥存储位置:别把公钥放在普通Flash里,容易被替换。我建议用芯片的OTP或安全存储区。
- 签名算法选择:RSA-PSS比PKCS#1 v1.5更安全,但部分老MCU不支持。选型时务必确认硬件支持。
- 哈希计算范围:签名覆盖的固件区域必须和验签时完全一致。包括固件头、版本号、甚至填充字节。
- 密钥生命周期:私钥要有轮换机制。我见过一个项目用了5年没换过私钥,最后被侧信道攻击破解了。
总结一下:签名验签不是「有就行」,而是「每一步都不能错」。从哈希算法、填充方式、密钥长度,到存储位置、硬件加速调用,任何一个环节出问题,整个安全启动就形同虚设。我个人习惯在项目初期就画好「签名验签流程图」,把每个步骤的责任方、算法参数、存储位置都写清楚,后面调试能省一半时间。
好了,这一章的内容就到这儿。下一章咱们聊聊安全启动的完整链路——从ROM代码到Bootloader再到App,每一级怎么互相信任。到时候我会拿一个实际的项目案例来拆解,保证干货满满。