3、OpenSSL 工具实操:生成密钥对、签名固件、验证签名

好,我们直接上手。OpenSSL 这工具,做嵌入式固件签名的工程师几乎天天打交道。它不花哨,但够稳。我个人习惯把它当作签名工具箱里的「瑞士军刀」—— 功能全,而且你只要掌握几个核心命令,就能搞定从密钥生成到签名验证的全流程。

这一节,我会带你走一遍完整的实操。你跟着敲一遍,基本就熟了。

核心流程速览

生成私钥 → 导出公钥 → 对固件做哈希 → 用私钥签名 → 用公钥验证签名。就这么几步,别想复杂了。

OpenSSL 固件签名与验证流程 OpenSSL 固件签名与验证流程 签名方(开发环境) 1. 生成密钥对 2. 计算固件哈希 3. 私钥签名 私钥 + 公钥 哈希值 签名文件 验证方(设备端/服务器) 4. 接收固件+签名 5. 重新计算哈希 6. 公钥验证签名 公钥(预置) 验证通过/失败

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 的签名操作就这么几行命令。但真正决定安全性的,是你怎么保护私钥、怎么安全地把公钥部署到设备上。这些「功夫在诗外」的东西,我们后面章节会细聊。