4、固件签名实战:使用OpenSSL对固件进行签名、验证签名流程、签名算法选择(RSA/ECDSA)
好,咱们进入实战环节。前面聊了那么多证书和PKI的理论,现在该动手了。这一章我会带着你,用OpenSSL这个工具,把固件签名的完整流程走一遍。从生成密钥对,到签名,再到验证,每一步我都会讲清楚。
我个人习惯在生产环境中使用OpenSSL 1.1.1以上版本,因为对椭圆曲线(ECDSA)的支持更完善。你如果还在用老版本,建议先升级一下。
4.1 签名算法选型:RSA还是ECDSA?
选算法是第一步。很多工程师上来就问「哪个更安全?」。其实到了256位密钥长度这个级别,两者安全性都够用。关键看你的硬件平台和业务场景。
| 对比维度 | RSA (2048位) | ECDSA (P-256) |
|---|---|---|
| 签名长度 | 256字节 | 64字节 |
| 签名速度 | 较慢 | 快 |
| 验证速度 | 快 | 较快 |
| 密钥生成速度 | 慢 | 极快 |
| 硬件加速支持 | 广泛 | 逐渐普及 |
| 固件体积影响 | 签名占用空间大 | 签名占用空间小 |
我的建议:
- 如果MCU主频低、Flash紧张,优先选ECDSA P-256。签名短,省空间。
- 如果芯片内置了RSA硬件加速引擎,或者你的Bootloader只支持RSA验签,那就用RSA 2048。
- 千万别用RSA 1024,已经不安全了。也别用ECDSA P-192,强度不够。
我在一个IoT网关项目里遇到过这种情况:芯片的Bootloader只硬编码了RSA验签逻辑,但OTA升级包太大,256字节的签名占了不少带宽。后来我们跟芯片原厂沟通,在新版Bootloader里加入了ECDSA支持,升级包体积一下就降下来了。
4.2 生成密钥对
选好算法,咱们就开干。先分别演示RSA和ECDSA的密钥生成命令。
4.2.1 生成RSA私钥
# 生成2048位RSA私钥,使用AES-256加密保护
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
-aes-256-cbc -out private_rsa.pem
# 从私钥导出公钥
openssl pkey -in private_rsa.pem -pubout -out public_rsa.pem
这里有个细节:-aes-256-cbc参数会让OpenSSL提示你输入密码。这个密码用来加密私钥文件。嗯,这里要注意,密码一定要妥善保管。我见过有人把密码写在代码注释里,然后整个仓库被拖库……
4.2.2 生成ECDSA私钥
# 生成P-256(prime256v1)椭圆曲线私钥
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 \
-aes-256-cbc -out private_ecdsa.pem
# 导出公钥
openssl pkey -in private_ecdsa.pem -pubout -out public_ecdsa.pem
小技巧:你可以用 openssl ecparam -list_curves 查看所有支持的椭圆曲线。我个人只推荐 prime256v1(即secp256r1)和 secp384r1。别的曲线要么兼容性差,要么安全性存疑。
4.3 固件签名流程
签名不是简单地对整个固件文件做加密。标准做法是:先计算固件的哈希值,再对这个哈希值做签名。这样做的好处是,签名长度固定,而且验签时不需要把整个固件加载到内存。
下面这张图展示了完整的签名和验证流程:
4.3.1 使用RSA签名
# 第一步:计算固件哈希
openssl dgst -sha256 -binary firmware.bin > firmware.hash
# 第二步:对哈希值做RSA签名
openssl pkeyutl -sign -inkey private_rsa.pem \
-in firmware.hash -out firmware.sig
# 或者一步到位(推荐):
openssl dgst -sha256 -sign private_rsa.pem \
-out firmware.sig firmware.bin
第二种方式更简洁,OpenSSL内部会先算哈希再签名。我建议你用这种,少一步中间文件操作,也少一个出错的可能。
4.3.2 使用ECDSA签名
# ECDSA签名(一步到位)
openssl dgst -sha256 -sign private_ecdsa.pem \
-out firmware.sig firmware.bin
命令和RSA一模一样,只是私钥文件不同。OpenSSL会根据私钥类型自动选择签名算法。这一点做得挺方便的。
注意:ECDSA签名是随机化的。同样的固件、同样的私钥,每次签名得到的签名值都不一样。这是正常现象,不是bug。RSA签名是确定性的,同样的输入永远得到同样的输出。
4.4 验证签名流程
验证签名通常在Bootloader或者安全启动代码中完成。但开发阶段,我们先用OpenSSL模拟验证,确保签名过程没问题。
4.4.1 RSA验签
# 使用公钥验证签名
openssl dgst -sha256 -verify public_rsa.pem \
-signature firmware.sig firmware.bin
如果输出 Verified OK,说明验签通过。如果输出 Verification Failure,那就得排查了。
4.4.2 ECDSA验签
# 使用公钥验证ECDSA签名
openssl dgst -sha256 -verify public_ecdsa.pem \
-signature firmware.sig firmware.bin
命令完全一样。这就是OpenSSL设计好的地方——上层接口统一,底层算法隔离。
4.5 避坑指南
我在几个项目里踩过一些坑,分享给你,希望你不用再走一遍。
- 哈希算法不一致:签名时用SHA-256,验签时用SHA-1,结果肯定失败。我曾经在脚本里写死了SHA-1,后来升级固件签名改成了SHA-256,但验签脚本忘了改……排查了半天。
- 公钥搞混:开发环境有多套密钥对,签名用了A套的私钥,验签却用了B套的公钥。这种错误很隐蔽,因为密钥长得都差不多。
- 签名格式问题:OpenSSL默认输出DER格式的签名。有些Bootloader要求原始格式(r||s)。你需要用
-hex或者自己解析DER结构。 - 文件换行符:在Windows上开发,Linux上构建,固件文件可能因为换行符不同导致哈希值不一致。建议统一在Linux环境下做签名。
我的习惯:在CI/CD流水线中,每次构建都会自动执行一次「签名→验签」的自检。如果自检失败,构建直接报红。这样能第一时间发现密钥配置错误或者脚本问题。
4.6 完整脚本示例
下面是一个完整的RSA签名和验证脚本,你可以直接拿去用。我把它写成了函数形式,方便集成到你的构建系统里。
#!/bin/bash
# sign_firmware.sh - 固件签名脚本
set -e
SIGN_KEY="private_rsa.pem"
VERIFY_KEY="public_rsa.pem"
FIRMWARE="firmware.bin"
SIGNATURE="firmware.sig"
# 签名
echo "=== 签名固件 ==="
openssl dgst -sha256 -sign "$SIGN_KEY" -out "$SIGNATURE" "$FIRMWARE"
echo "签名完成: $SIGNATURE"
# 自检
echo "=== 自检验证 ==="
openssl dgst -sha256 -verify "$VERIFY_KEY" -signature "$SIGNATURE" "$FIRMWARE"
if [ $? -eq 0 ]; then
echo "✓ 自检通过"
else
echo "✗ 自检失败!请检查密钥配置"
exit 1
fi
这个脚本我用了好几年,基本没出过问题。你只需要把密钥文件和固件路径换成自己的就行。