4、固件签名与加密:使用RSA/ECDSA签名,AES加密固件包,防止逆向与篡改。
好,咱们进入第四章。这一章讲的是固件的「防身术」——签名与加密。
说实话,我在早期做POS机项目时,对这块重视不够。总觉得固件嘛,烧进去就完事了。直到有一次,客户反馈说机器被刷了第三方固件,出现了支付漏洞……嗯,那次的教训让我彻底明白了:没有签名和加密的固件,就像没上锁的保险柜。
4.1 为什么要做签名和加密?
你想想看,POS机是干啥的?处理交易、读取卡片、连接支付网关。如果固件被人篡改了,后果不堪设想。
我总结了两大核心目标:
- 防篡改:确保固件包在传输和存储过程中,没有被恶意修改
- 防逆向:防止攻击者通过分析固件,提取敏感信息或找到漏洞
签名解决的是「谁写的」和「改没改」的问题。加密解决的是「别人能不能看懂」的问题。两者缺一不可。
核心原则:签名用非对称算法(RSA/ECDSA),加密用对称算法(AES)。
为什么?签名需要私钥签名、公钥验证,适合一对多的场景。加密需要速度快,AES硬件加速在很多MCU上都有。
4.2 签名算法:RSA vs ECDSA
我个人习惯用ECDSA,原因很简单——同样的安全强度,密钥更短。对于资源受限的嵌入式设备,这太重要了。
来看个对比:
| 特性 | RSA-2048 | ECDSA-P256 |
|---|---|---|
| 密钥长度 | 2048 bits | 256 bits |
| 签名速度 | 较慢 | 较快 |
| 验证速度 | 较快 | 较慢 |
| 存储占用 | 大 | 小 |
| 硬件支持 | 广泛 | 逐渐普及 |
我在项目中遇到过一个问题:某款MCU的硬件加密引擎只支持RSA,不支持ECDSA。没办法,只能硬着头皮用RSA。所以选型时一定要先看芯片手册。
我的建议:如果芯片支持硬件加速,优先用硬件支持的算法。纯软件实现ECDSA签名验证,在低主频MCU上可能会卡顿。
4.3 加密算法:AES-128还是AES-256?
固件加密我基本只用AES。原因?它是国家标准,硬件支持好,安全性经过充分验证。
至于密钥长度,我个人倾向AES-128。你可能会问:为什么不用256?
说白了,AES-128在可预见的未来足够安全。而且128位密钥在MCU上处理更快,占用资源更少。我见过一些项目盲目追求256,结果导致OTA升级时间翻倍,用户体验很差。
加密模式我推荐GCM模式。它同时提供加密和完整性校验,一次搞定。以前用CBC模式还得额外算HMAC,麻烦得很。
注意:千万不要用ECB模式!它会把相同明文加密成相同密文,存在严重的安全隐患。我曾经接手过一个遗留项目,就是用ECB模式,结果固件被轻松破解了。
4.4 完整的签名加密流程
好,咱们把整个流程串起来。我一般分三步走:
- 生成固件包:编译出二进制文件,比如 firmware.bin
- 签名:用私钥对固件哈希值签名,生成签名文件
- 加密:用AES密钥加密固件包,生成最终的升级文件
来看一个实际的Python脚本示例:
import hashlib
from Crypto.Signature import DSS
from Crypto.PublicKey import ECC
from Crypto.Cipher import AES
# 1. 读取固件
with open('firmware.bin', 'rb') as f:
firmware = f.read()
# 2. 计算哈希
hash_obj = hashlib.sha256(firmware)
digest = hash_obj.digest()
# 3. 签名
private_key = ECC.import_key(open('private.pem').read())
signer = DSS.new(private_key, 'fips-186-3')
signature = signer.sign(hash_obj)
# 4. 加密
aes_key = b'0123456789abcdef' # 实际项目中从HSM获取
cipher = AES.new(aes_key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(firmware)
# 5. 打包
with open('update.pkg', 'wb') as f:
f.write(cipher.nonce) # 随机数
f.write(tag) # 认证标签
f.write(signature) # 签名
f.write(ciphertext) # 加密后的固件
嗯,这里要注意:签名是对固件的哈希值签名,不是对加密后的数据签名。顺序很重要——先签名后加密。这样接收方可以先解密,再验证签名。
4.5 接收端的验证流程
设备端收到升级包后,要做的事情正好相反:
- 用AES密钥解密,得到固件和签名
- 计算固件的哈希值
- 用公钥验证签名是否匹配
我在实际项目中踩过一个坑:公钥的存储位置。如果把公钥直接放在固件里,攻击者可以替换公钥,从而绕过签名验证。
正确的做法是:
- 公钥烧写在芯片的一次性可编程(OTP)区域
- 或者存储在安全元件(SE)中
- 再或者通过硬件唯一密钥派生
避坑指南:我曾经在一个项目里,把公钥放在了外部Flash中。结果攻击者通过SPI接口读出了公钥,然后用自己的私钥签名了恶意固件……从那以后,我再也不敢把公钥放在可读区域了。
4.6 密钥管理:最容易被忽视的环节
说实话,算法选型、代码实现这些都不难。真正难的是密钥管理。
我见过太多项目,密钥硬编码在代码里,或者放在一个谁都读得到的配置文件中。这等于把保险柜的钥匙挂在门上。
我的建议:
- 生产环境:使用硬件安全模块(HSM)生成和存储密钥
- 开发环境:使用独立的测试密钥,不要和生产环境混用
- 密钥分发:通过安全通道传输,比如TLS加密的服务器
- 密钥轮换:定期更换密钥,尤其是当有员工离职时
小技巧:可以在固件中预置多个公钥,对应不同版本的私钥。这样即使某个私钥泄露,也能快速切换到备用密钥,不用召回所有设备。
4.7 性能优化:别让安全拖慢升级
安全是有代价的。签名验证和加解密都会消耗时间和电量。对于电池供电的POS机,这尤其重要。
我常用的优化手段:
- 分块处理:不要一次性加载整个固件到内存,而是分块解密和验证
- 硬件加速:尽量使用MCU内置的AES和哈希硬件引擎
- 延迟验证:先解密写入Flash,重启时再验证签名。这样升级过程更快
你可能会问:延迟验证安全吗?嗯,只要保证解密后的数据在Flash中不被篡改,就没问题。我一般会在Flash区域加上写保护,防止意外修改。
4.8 总结
这一章的内容其实就一句话:签名保真,加密保密,密钥管好。
我在做OTA方案时,每次都会问自己三个问题:
- 攻击者能不能伪造一个固件包?——签名解决
- 攻击者能不能读懂固件内容?——加密解决
- 攻击者能不能拿到密钥?——密钥管理解决
如果这三个问题都回答「不能」,那你的固件安全就基本到位了。
下一章,咱们聊聊升级过程中的异常处理和回滚机制。那才是真正考验方案健壮性的地方。