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 完整的签名加密流程

好,咱们把整个流程串起来。我一般分三步走:

  1. 生成固件包:编译出二进制文件,比如 firmware.bin
  2. 签名:用私钥对固件哈希值签名,生成签名文件
  3. 加密:用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 接收端的验证流程

设备端收到升级包后,要做的事情正好相反:

  1. 用AES密钥解密,得到固件和签名
  2. 计算固件的哈希值
  3. 用公钥验证签名是否匹配

我在实际项目中踩过一个坑:公钥的存储位置。如果把公钥直接放在固件里,攻击者可以替换公钥,从而绕过签名验证。

正确的做法是:

  • 公钥烧写在芯片的一次性可编程(OTP)区域
  • 或者存储在安全元件(SE)中
  • 再或者通过硬件唯一密钥派生

避坑指南:我曾经在一个项目里,把公钥放在了外部Flash中。结果攻击者通过SPI接口读出了公钥,然后用自己的私钥签名了恶意固件……从那以后,我再也不敢把公钥放在可读区域了。

4.6 密钥管理:最容易被忽视的环节

说实话,算法选型、代码实现这些都不难。真正难的是密钥管理

我见过太多项目,密钥硬编码在代码里,或者放在一个谁都读得到的配置文件中。这等于把保险柜的钥匙挂在门上。

我的建议:

  • 生产环境:使用硬件安全模块(HSM)生成和存储密钥
  • 开发环境:使用独立的测试密钥,不要和生产环境混用
  • 密钥分发:通过安全通道传输,比如TLS加密的服务器
  • 密钥轮换:定期更换密钥,尤其是当有员工离职时

小技巧:可以在固件中预置多个公钥,对应不同版本的私钥。这样即使某个私钥泄露,也能快速切换到备用密钥,不用召回所有设备。

4.7 性能优化:别让安全拖慢升级

安全是有代价的。签名验证和加解密都会消耗时间和电量。对于电池供电的POS机,这尤其重要。

我常用的优化手段:

  • 分块处理:不要一次性加载整个固件到内存,而是分块解密和验证
  • 硬件加速:尽量使用MCU内置的AES和哈希硬件引擎
  • 延迟验证:先解密写入Flash,重启时再验证签名。这样升级过程更快

你可能会问:延迟验证安全吗?嗯,只要保证解密后的数据在Flash中不被篡改,就没问题。我一般会在Flash区域加上写保护,防止意外修改。

4.8 总结

这一章的内容其实就一句话:签名保真,加密保密,密钥管好

我在做OTA方案时,每次都会问自己三个问题:

  1. 攻击者能不能伪造一个固件包?——签名解决
  2. 攻击者能不能读懂固件内容?——加密解决
  3. 攻击者能不能拿到密钥?——密钥管理解决

如果这三个问题都回答「不能」,那你的固件安全就基本到位了。

下一章,咱们聊聊升级过程中的异常处理和回滚机制。那才是真正考验方案健壮性的地方。