3、固件签名流程实战:使用OpenSSL对固件进行签名、签名验证流程、多级引导中的签名链(Chain of Trust)
好,咱们进入实战环节。前面两章讲了理论基础和威胁模型,这一章咱们直接动手——用 OpenSSL 把固件签上名,再验证回来,最后把这一套串成多级引导的信任链。
说实话,签名这件事本身不复杂。复杂的是你怎么把它嵌到整个启动流程里,让每一级都信任上一级。我见过不少团队,签名工具写得挺溜,结果引导链断了,板子变砖。嗯,咱们今天就把这个闭环走通。
3.1 用 OpenSSL 生成签名密钥对
签名之前,你得先有一对钥匙。私钥你自己藏好,公钥扔到固件里或者烧进芯片的一次性存储里。
我个人习惯用 RSA-4096,虽然 ECC 更快,但 RSA 在老旧 BootROM 里兼容性更好。你想想看,很多 SoC 的 BootROM 是十年前写的,ECC 支持可能是个坑。
# 生成 RSA 私钥,AES-256 加密保护
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 \
-aes-256-cbc -out firmware_signing_key.pem
# 导出公钥
openssl rsa -pubout -in firmware_signing_key.pem \
-out firmware_signing_pubkey.pem
# 查看公钥信息
openssl rsa -pubin -in firmware_signing_pubkey.pem -text -noout
3.2 固件签名流程实战
签名不是把整个固件加密,而是对固件的哈希值做签名。这样既快又安全——你想想看,几 MB 的固件用私钥加密得多慢?
标准流程分三步:
- 计算固件哈希——通常用 SHA-256 或 SHA-384
- 用私钥签名哈希——生成签名数据
- 打包——把固件、签名、公钥(或公钥哈希)打包成一个镜像
# 步骤1:计算固件哈希
openssl dgst -sha256 -binary firmware.bin > firmware.hash
# 步骤2:用私钥签名哈希
openssl pkeyutl -sign -inkey firmware_signing_key.pem \
-in firmware.hash -out firmware.sig
# 步骤3:打包(简单拼接,实际项目会用自定义镜像格式)
cat firmware.bin firmware.sig firmware_signing_pubkey.pem > firmware_signed.bin
3.3 签名验证流程
验证是签名的逆过程。BootROM 拿到固件后,先拆包,再用公钥验签名。这里有个关键点:公钥从哪来?
如果公钥是明文放在固件里的,那攻击者可以替换公钥,用自己的私钥签恶意固件。所以公钥必须来自一个可信源——要么烧死在芯片 OTP 里,要么由上一级引导程序传递。
# 验证流程(模拟 BootROM 侧操作)
# 1. 从镜像中提取公钥、签名、原始固件
# 2. 计算原始固件的哈希
openssl dgst -sha256 -binary extracted_firmware.bin > computed.hash
# 3. 用公钥验证签名
openssl pkeyutl -verify -pubin -inkey extracted_pubkey.pem \
-sigfile extracted_signature.sig -in computed.hash
# 如果输出 "Signature Verified Successfully",说明固件未被篡改
验证失败时怎么办?
- 如果签名验证失败,BootROM 必须拒绝启动——不能 fallback 到未签名固件
- 我建议设计一个恢复模式:如果主镜像验签失败,尝试从恢复分区加载备份镜像
- 千万不要在验证失败后继续执行,哪怕只是打印错误——攻击者可以利用这个窗口
3.4 多级引导中的签名链(Chain of Trust)
单级签名好理解,但实际嵌入式设备通常是多级引导:BootROM → SPL → U-Boot → Kernel → 文件系统。每一级都要验证下一级,这就形成了信任链。
说白了,信任链的核心思想是:每一级只信任它自己的公钥,然后用这个公钥验证下一级的签名。 只要第一级(BootROM)是可信的,整个链就安全。
下面这张图展示了典型的四级信任链结构:
3.5 信任链的工程实现细节
理论讲完了,咱们看看实际代码里怎么实现。以 U-Boot 验证 Kernel 为例:
// U-Boot 中验证 Kernel 镜像的伪代码
int verify_kernel_image(void *kernel_addr, size_t kernel_size) {
// 1. 从固定偏移读取签名和公钥
signature_t *sig = (signature_t *)(kernel_addr + kernel_size);
public_key_t *pubkey = (public_key_t *)(kernel_addr + kernel_size + sizeof(signature_t));
// 2. 检查公钥是否匹配 U-Boot 内置的信任公钥哈希
uint8_t pubkey_hash[SHA256_DIGEST_LENGTH];
sha256(pubkey, sizeof(public_key_t), pubkey_hash);
if (memcmp(pubkey_hash, trusted_pubkey_hash, SHA256_DIGEST_LENGTH) != 0) {
printf("ERROR: Public key not trusted!\n");
return -1; // 拒绝启动
}
// 3. 验证签名
uint8_t image_hash[SHA256_DIGEST_LENGTH];
sha256(kernel_addr, kernel_size, image_hash);
int ret = rsa_verify(pubkey, sig, image_hash);
if (ret != 0) {
printf("ERROR: Signature verification failed!\n");
return -2; // 拒绝启动
}
printf("Kernel image verified successfully.\n");
return 0;
}
3.6 签名链的常见攻击面与防御
信任链不是万能的。攻击者会盯着链上的薄弱环节。我整理了一个表格,列出最常见的攻击手法和对应的防御措施:
| 攻击面 | 攻击手法 | 防御措施 |
|---|---|---|
| 回滚攻击 | 攻击者用旧版本(有已知漏洞)的已签名固件替换新固件 | 在签名结构中包含版本号,BootROM 检查版本号是否递增 |
| 公钥替换 | 攻击者替换固件包中的公钥,用自己的私钥签名 | 公钥哈希烧死在 OTP,BootROM 只信任 OTP 中的哈希 |
| 中间人攻击 | 在固件传输过程中篡改数据 | 签名验证天然防篡改,但要注意传输通道也要加密 |
| 侧信道攻击 | 通过功耗、电磁辐射分析私钥 | 使用恒定时间比较函数,避免基于数据的分支 |
| BootROM 漏洞 | 利用 BootROM 本身的缓冲区溢出等漏洞 | 选择经过安全审计的 BootROM,定期更新芯片 |
3.7 实战:搭建一个完整的签名链测试环境
纸上谈兵没意思。咱们用 QEMU 模拟一个简单的双级引导环境,跑通签名链:
- 生成两级密钥对:BootROM 密钥对 + U-Boot 密钥对
- 签名 U-Boot 镜像:用 BootROM 私钥签名 U-Boot
- 签名 Kernel 镜像:用 U-Boot 私钥签名 Kernel
- 模拟验证:写一个测试程序模拟 BootROM 验证 U-Boot,U-Boot 验证 Kernel
#!/bin/bash
# 签名链测试脚本(双级)
# 1. 生成密钥对
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
-out bootrom_key.pem
openssl rsa -pubout -in bootrom_key.pem -out bootrom_pubkey.pem
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
-out uboot_key.pem
openssl rsa -pubout -in uboot_key.pem -out uboot_pubkey.pem
# 2. 签名 U-Boot(用 BootROM 私钥)
openssl dgst -sha256 -binary uboot.bin > uboot.hash
openssl pkeyutl -sign -inkey bootrom_key.pem \
-in uboot.hash -out uboot.sig
# 3. 签名 Kernel(用 U-Boot 私钥)
openssl dgst -sha256 -binary kernel.bin > kernel.hash
openssl pkeyutl -sign -inkey uboot_key.pem \
-in kernel.hash -out kernel.sig
# 4. 打包(实际项目会使用 FIT 或自定义格式)
# 这里只是演示结构
echo "BootROM 验证 U-Boot:"
openssl pkeyutl -verify -pubin -inkey bootrom_pubkey.pem \
-sigfile uboot.sig -in uboot.hash && echo "OK" || echo "FAIL"
echo "U-Boot 验证 Kernel:"
openssl pkeyutl -verify -pubin -inkey uboot_pubkey.pem \
-sigfile kernel.sig -in kernel.hash && echo "OK" || echo "FAIL"
测试结果解读:
- 如果两个验证都返回 "OK",说明信任链建立成功
- 如果篡改 uboot.bin 或 kernel.bin,验证会失败——这就是签名链的保护效果
- 你可以试试替换公钥,看看验证是否会失败——这是对公钥替换攻击的模拟
好了,这一章的内容就到这。签名链的核心就一句话:信任从硬件开始,一级一级传递下去,每一级只信上一级给的东西。 你把这个逻辑理清楚,代码实现就是水到渠成的事。