3、OpenSSL 工具实操:生成密钥对、签名固件、验证签名
好,我们直接上手。OpenSSL 这工具,做嵌入式固件签名的工程师几乎天天打交道。它不花哨,但够稳。我个人习惯把它当作签名工具箱里的「瑞士军刀」—— 功能全,而且你只要掌握几个核心命令,就能搞定从密钥生成到签名验证的全流程。
这一节,我会带你走一遍完整的实操。你跟着敲一遍,基本就熟了。
核心流程速览
生成私钥 → 导出公钥 → 对固件做哈希 → 用私钥签名 → 用公钥验证签名。就这么几步,别想复杂了。
3.1 生成密钥对
签名固件,第一步是搞到一对钥匙。私钥你自己藏好,公钥丢到设备里。我建议用 RSA-2048 或 ECC-256,别再用 1024 位了,不安全。
下面这个命令生成 RSA 私钥:
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
然后从私钥里导出公钥:
openssl rsa -pubout -in private_key.pem -out public_key.pem
我的习惯:私钥文件我会加上 -aes-256-cbc 参数加密存储。虽然每次签名要输密码,但安全多了。你想想,私钥要是裸奔在硬盘上,被拖走了就全完了。
如果你偏好 ECC,用这个:
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -out ecdsa_private.pem
openssl ec -pubout -in ecdsa_private.pem -out ecdsa_public.pem
嗯,这里要注意:ECC 的签名体积小,对资源受限的 MCU 很友好。我在一个 IoT 项目里用过,设备端验签速度比 RSA 快不少。
3.2 签名固件
签名不是直接拿私钥去加密固件文件。那样太慢了。正确的做法是:先对固件算一个哈希值,再对这个哈希值签名。
OpenSSL 支持一步到位:
openssl dgst -sha256 -sign private_key.pem -out firmware.sig firmware.bin
这条命令干了三件事:
- 对
firmware.bin做 SHA-256 哈希 - 用私钥对这个哈希做签名
- 把签名结果写入
firmware.sig
签名文件是二进制的。如果你需要分发或存储成文本格式,可以转成 Base64:
openssl base64 -in firmware.sig -out firmware.sig.b64
我曾经踩过的坑:有一次我签名时忘了指定哈希算法,OpenSSL 默认用了 SHA-1。结果设备端验签一直失败,查了半天才发现是哈希算法不匹配。所以,永远显式指定 -sha256,别偷懒。
3.3 验证签名
验证是签名的逆操作。设备端拿到固件和签名后,用公钥验签:
openssl dgst -sha256 -verify public_key.pem -signature firmware.sig firmware.bin
如果输出 Verified OK,说明固件没被篡改,且确实来自持有私钥的你。
如果输出 Verification Failure,那就要警惕了。可能的原因:
- 固件文件被修改过
- 签名文件损坏或不匹配
- 公钥和私钥不配对
- 哈希算法不一致
验证命令的另一种写法:你也可以用 openssl pkeyutl 系列命令,但 dgst 更简洁,我推荐日常用这个。
3.4 实操中的常见问题
我整理了一个表格,把容易出问题的地方列出来,你对照着看:
| 问题场景 | 典型错误 | 解决方案 |
|---|---|---|
| 密钥格式不匹配 | 设备端只支持 DER,你给了 PEM | 用 openssl rsa -in key.pem -outform DER -out key.der 转换 |
| 签名算法不一致 | 签名用 SHA-256,设备端用 SHA-1 | 统一使用 SHA-256,两端对齐 |
| 公钥未预置 | 设备端没有烧录公钥 | 在固件烧录阶段将公钥写入安全存储区 |
| 签名文件附加方式 | 把签名追加到固件尾部,但解析偏移算错 | 建议使用独立签名文件,或明确定义签名在固件中的偏移和长度 |
我的建议:在开发阶段,写一个自动化脚本把签名和验证流程串起来。每次编译完固件自动签名,省得手敲命令敲错。我团队现在就是这么干的,CI 流水线里直接集成。
3.5 完整实操示例
最后,我给你一个完整的脚本示例。你把它保存成 sign_firmware.sh,改改路径就能用:
#!/bin/bash
# 固件签名脚本
# 用法:./sign_firmware.sh firmware.bin
FW=$1
PRIV_KEY="private_key.pem"
PUB_KEY="public_key.pem"
SIG_FILE="${FW}.sig"
# 1. 检查密钥是否存在,不存在则生成
if [ ! -f "$PRIV_KEY" ]; then
echo "生成 RSA 2048 密钥对..."
openssl genpkey -algorithm RSA -out "$PRIV_KEY" -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in "$PRIV_KEY" -out "$PUB_KEY"
fi
# 2. 签名
echo "签名固件:$FW"
openssl dgst -sha256 -sign "$PRIV_KEY" -out "$SIG_FILE" "$FW"
# 3. 验证
echo "验证签名..."
openssl dgst -sha256 -verify "$PUB_KEY" -signature "$SIG_FILE" "$FW"
if [ $? -eq 0 ]; then
echo "签名成功,验证通过!"
else
echo "签名验证失败!"
exit 1
fi
你跑一遍这个脚本,就能看到完整的签名和验证输出。说白了,OpenSSL 的签名操作就这么几行命令。但真正决定安全性的,是你怎么保护私钥、怎么安全地把公钥部署到设备上。这些「功夫在诗外」的东西,我们后面章节会细聊。