4. 固件签名与验签

任务目标

本案例实现一个完整的固件签名与验签闭环:在主机端使用ECDSA P-256私钥对固件镜像计算签名,在设备端(模拟环境)使用对应公钥验证签名,确保固件未被篡改。核心目标是演示签名如何绑定固件内容——任何单字节修改都会导致验签失败。

环境与依赖

  • Python 3.8+,需安装 cryptography 库:pip install cryptography
  • 本案例使用纯Python模拟设备端验签流程,不依赖特定硬件

关键完整代码

import hashlib
import os
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.serialization import (
    Encoding, PublicFormat, PrivateFormat, NoEncryption
)

# ========== 1. 生成密钥对(仅需执行一次,实际部署时私钥保存在安全服务器) ==========
def generate_keypair():
    private_key = ec.generate_private_key(ec.SECP256R1())
    # 保存私钥(PEM格式)
    with open("firmware_private.pem", "wb") as f:
        f.write(private_key.private_bytes(
            Encoding.PEM, PrivateFormat.PKCS8, NoEncryption()
        ))
    # 保存公钥(PEM格式)
    with open("firmware_public.pem", "wb") as f:
        f.write(private_key.public_key().public_bytes(
            Encoding.PEM, PublicFormat.SubjectPublicKeyInfo
        ))
    return private_key

# ========== 2. 主机端:对固件签名 ==========
def sign_firmware(firmware_path, private_key_path):
    with open(private_key_path, "rb") as f:
        private_key = serialization_load_pem(f.read())
    with open(firmware_path, "rb") as f:
        firmware_data = f.read()
    # 对固件内容做SHA-256哈希后签名
    signature = private_key.sign(firmware_data, ec.ECDSA(hashes.SHA256()))
    # 将签名追加到固件末尾(签名长度固定为64字节,P-256曲线)
    signed_firmware = firmware_data + signature
    with open(firmware_path + ".signed", "wb") as f:
        f.write(signed_firmware)
    print(f"签名完成,签名长度: {len(signature)} 字节")
    return len(signature)

# ========== 3. 设备端:验证签名 ==========
def verify_firmware(signed_firmware_path, public_key_path, sig_len=64):
    with open(public_key_path, "rb") as f:
        public_key = serialization_load_pem(f.read())
    with open(signed_firmware_path, "rb") as f:
        signed_data = f.read()
    # 分离固件原始数据和签名
    firmware_data = signed_data[:-sig_len]
    signature = signed_data[-sig_len:]
    try:
        public_key.verify(signature, firmware_data, ec.ECDSA(hashes.SHA256()))
        return True, "验签通过:固件完整且未被篡改"
    except Exception:
        return False, "验签失败:固件被篡改或签名不匹配"

# ========== 辅助函数 ==========
def serialization_load_pem(pem_bytes):
    from cryptography.hazmat.primitives.serialization import load_pem_private_key, load_pem_public_key
    try:
        return load_pem_private_key(pem_bytes, password=None)
    except Exception:
        return load_pem_public_key(pem_bytes)

# ========== 4. 最小验证闭环 ==========
if __name__ == "__main__":
    # 生成测试固件(模拟真实固件镜像)
    test_firmware = b"\x00\x01\x02\x03" * 256  # 1KB 模拟固件
    with open("test_fw.bin", "wb") as f:
        f.write(test_firmware)
    
    # 首次运行生成密钥对(若已存在则跳过)
    if not os.path.exists("firmware_private.pem"):
        generate_keypair()
    
    # 签名
    sig_len = sign_firmware("test_fw.bin", "firmware_private.pem")
    
    # 验证原始固件
    result, msg = verify_firmware("test_fw.bin.signed", "firmware_public.pem", sig_len)
    print(f"[原始固件] {msg}")
    
    # 篡改测试:修改固件中一个字节
    with open("test_fw.bin.signed", "rb") as f:
        tampered = bytearray(f.read())
    tampered[100] ^= 0xFF  # 翻转第100个字节
    with open("tampered_fw.bin", "wb") as f:
        f.write(bytes(tampered))
    
    # 验证被篡改的固件
    result2, msg2 = verify_firmware("tampered_fw.bin", "firmware_public.pem", sig_len)
    print(f"[篡改固件] {msg2}")
    
    # 清理测试文件(可选)
    # os.remove("test_fw.bin"); os.remove("test_fw.bin.signed"); os.remove("tampered_fw.bin")

运行与验证

  1. 将上述代码保存为 firmware_sign_verify.py
  2. 运行:python firmware_sign_verify.py
  3. 预期输出(确定性结果):
签名完成,签名长度: 64 字节
[原始固件] 验签通过:固件完整且未被篡改
[篡改固件] 验签失败:固件被篡改或签名不匹配

判定标准

  • 原始固件验签返回 True,篡改固件验签返回 False,即证明签名机制有效
  • 签名长度为64字节(P-256 ECDSA的r和s各32字节),与代码逻辑一致
  • 若篡改的是签名本身而非固件数据,验签同样失败——因为签名与固件内容不匹配

实际部署要点

  • 私钥保护:私钥仅在主机端签名时使用,严禁下发到设备端;设备端仅存储公钥
  • 签名位置:本案例将签名追加在固件末尾,实际产品可放在头部或独立分区,需在Bootloader中约定偏移量
  • 防回滚:如需防止旧版本回滚攻击,可在固件头增加版本号字段并一并签名
  • 性能:ECDSA P-256验签在常见MCU(如Cortex-M4 @ 100MHz)上约需50~200ms,若需更快可选用Ed25519