3. 数字签名基础:哈希算法、非对称加密与签名验证流程
各位好,我是老赵。今天咱们聊的是OTA安全里最核心的一块——数字签名。说白了,就是给固件贴个防伪标签,确保它没被人动过手脚。
我在做车载OTA项目时,遇到过最头疼的事就是中间人攻击。攻击者截获了固件包,往里面塞了恶意代码,然后重新打包发下去。如果没有签名机制,设备根本不知道收到的固件是真是假。嗯,数字签名就是解决这个问题的。
3.1 哈希算法:固件的“数字指纹”
先说说哈希算法。它的作用很简单:给任意长度的数据算出一个固定长度的摘要。这个摘要就像人的指纹,数据变了,指纹就变。
我个人习惯用SHA-256,它是SHA-2家族的一员,输出256位(32字节)的哈希值。为什么选它?因为碰撞概率极低——你想找到两个不同文件但哈希值一样,理论上得算2^128次,这在现实中几乎不可能。
哈希算法的核心特性:
- 单向性:从哈希值反推原始数据,算不出来。你想想看,如果哈希能逆推,那密码存储就全废了。
- 抗碰撞性:找不到两个不同输入有相同输出。我在项目中见过有人用MD5,结果被碰撞攻击搞崩了——两个不同固件算出同一个MD5值,签名直接失效。
- 雪崩效应:输入改一个bit,输出一半以上的bit都变。这特性用来检测篡改特别灵。
举个实际例子。假设你的固件是1MB,直接对它签名效率很低。先算SHA-256,得到32字节的哈希值,再对这个哈希值签名,速度快得多。这就是“先哈希,后签名”的标准做法。
# 计算固件的SHA-256哈希值
echo -n "firmware_v3.2.bin" | sha256sum
# 输出示例:a3f5b8c1d2e4f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b
避坑指南:我曾经在项目里用SHA-1,后来发现Google已经演示了SHA-1碰撞攻击。从那以后,我所有项目都强制要求SHA-256或更高。别图省那点计算资源,安全没得商量。
3.2 非对称加密:公钥与私钥的配对
非对称加密,说白了就是两把钥匙:一把公开(公钥),一把私藏(私钥)。公钥加密的数据只能用私钥解密,私钥签名的数据只能用公钥验证。
OTA里常用的非对称算法有两种:RSA和ECC。我做个对比表,大家一看就明白。
| 特性 | RSA | ECC(椭圆曲线) |
|---|---|---|
| 密钥长度 | 2048/4096位 | 256/384位 |
| 签名速度 | 较慢 | 较快 |
| 验证速度 | 较快 | 较慢 |
| 安全强度 | 高(2048位≈112位对称) | 高(256位≈128位对称) |
| 资源消耗 | 高(适合服务器) | 低(适合嵌入式设备) |
我个人建议:如果设备算力有限(比如MCU),优先选ECC。我在智能电表项目里用过ECC P-256,签名验证一次只要几十毫秒,而RSA 2048要几百毫秒。但如果是云端签名,RSA更成熟,库也更全。
注意:密钥长度不是越长越好。RSA 4096虽然安全,但签名包会变大,传输和存储都有开销。ECC 256位已经达到128位安全等级,够用就行。我曾经见过有人用RSA 8192,结果固件升级包大了好几倍,用户流量费都付不起。
3.3 数字签名生成与验证流程
好了,前面铺垫完了,现在说正题。数字签名到底怎么生成?怎么验证?我画了张流程图,大家跟着走一遍。
流程其实不复杂,我拆成两步讲。
3.3.1 签名生成(服务端)
- 计算哈希:对固件文件做SHA-256,得到32字节的哈希值。
- 私钥签名:用服务端的私钥对哈希值进行加密(RSA)或椭圆曲线运算(ECC),生成数字签名。
- 打包分发:把固件和签名一起发给设备。注意,签名本身不加密固件,它只是附在后面的校验数据。
# 使用OpenSSL生成RSA签名
openssl dgst -sha256 -sign private_key.pem -out firmware.sig firmware.bin
# 输出:firmware.sig(256字节的签名文件)
3.3.2 签名验证(设备端)
- 接收数据:设备收到固件和签名。
- 计算哈希:对收到的固件重新算SHA-256,得到哈希值H1。
- 公钥验证:用预置在设备中的公钥解密签名,得到哈希值H2。比较H1和H2,一致则通过,否则拒绝升级。
# 设备端验证签名
openssl dgst -sha256 -verify public_key.pem -signature firmware.sig firmware.bin
# 输出:Verified OK 或 Verification Failure
关键点:公钥必须安全地预置在设备中,不能通过网络传输。我见过有人把公钥放在固件里一起下发,结果攻击者替换了公钥,签名验证形同虚设。正确的做法是在设备出厂时烧录公钥,或者用证书链逐级验证。
3.4 实际项目中的避坑指南
做了这么多年OTA,我踩过不少坑。挑几个典型的说说。
坑1:哈希算法选错——我曾经在项目里用SHA-1,后来发现Google已经演示了SHA-1碰撞攻击。从那以后,我所有项目都强制要求SHA-256或更高。别图省那点计算资源,安全没得商量。
坑2:签名长度没算好——RSA 2048签名是256字节,ECC P-256签名是64字节。如果固件包有大小限制(比如BLE传输限制512字节),签名长度就得精打细算。我建议用ECC,签名短,适合物联网设备。
坑3:公钥硬编码被反编译——公钥虽然公开,但如果被攻击者从固件里提取出来,他可以伪造签名吗?不能,因为没有私钥。但他可以替换公钥。所以公钥要放在安全存储区(如eFuse或TEE),不能被篡改。