4. 基于ECDSA的固件签名生成与验签
任务目标
本案例实现一个完整的固件签名与验签闭环:在主机端使用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")
运行与验证
- 将上述代码保存为
firmware_sign_verify.py - 运行:
python firmware_sign_verify.py - 预期输出(确定性结果):
签名完成,签名长度: 64 字节
[原始固件] 验签通过:固件完整且未被篡改
[篡改固件] 验签失败:固件被篡改或签名不匹配
判定标准
- 原始固件验签返回
True,篡改固件验签返回False,即证明签名机制有效 - 签名长度为64字节(P-256 ECDSA的r和s各32字节),与代码逻辑一致
- 若篡改的是签名本身而非固件数据,验签同样失败——因为签名与固件内容不匹配
实际部署要点
- 私钥保护:私钥仅在主机端签名时使用,严禁下发到设备端;设备端仅存储公钥
- 签名位置:本案例将签名追加在固件末尾,实际产品可放在头部或独立分区,需在Bootloader中约定偏移量
- 防回滚:如需防止旧版本回滚攻击,可在固件头增加版本号字段并一并签名
- 性能:ECDSA P-256验签在常见MCU(如Cortex-M4 @ 100MHz)上约需50~200ms,若需更快可选用Ed25519