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 数字签名生成与验证流程

好了,前面铺垫完了,现在说正题。数字签名到底怎么生成?怎么验证?我画了张流程图,大家跟着走一遍。

数字签名生成与验证流程 数字签名生成与验证流程 签名生成(服务端) 固件文件(Firmware.bin) SHA-256 哈希计算 哈希值(32字节) 私钥签名(RSA/ECC) 数字签名(256/512字节) 签名验证(设备端) 固件 + 签名 SHA-256 哈希计算 哈希值(32字节) 公钥验证签名 验证通过/失败 签名与固件一起传输 核心原则:私钥签名,公钥验证;私钥永远不离开服务端

流程其实不复杂,我拆成两步讲。

3.3.1 签名生成(服务端)

  1. 计算哈希:对固件文件做SHA-256,得到32字节的哈希值。
  2. 私钥签名:用服务端的私钥对哈希值进行加密(RSA)或椭圆曲线运算(ECC),生成数字签名。
  3. 打包分发:把固件和签名一起发给设备。注意,签名本身不加密固件,它只是附在后面的校验数据。
# 使用OpenSSL生成RSA签名
openssl dgst -sha256 -sign private_key.pem -out firmware.sig firmware.bin
# 输出:firmware.sig(256字节的签名文件)

3.3.2 签名验证(设备端)

  1. 接收数据:设备收到固件和签名。
  2. 计算哈希:对收到的固件重新算SHA-256,得到哈希值H1。
  3. 公钥验证:用预置在设备中的公钥解密签名,得到哈希值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),不能被篡改。