4. 升级包签名与验签流程设计

任务目标

设计并实现一个OTA升级包的签名与验签流程,确保升级包在传输和存储过程中未被篡改,且来源可信。本案例使用ECDSA P-256(secp256r1)签名算法,完成从密钥生成、升级包签名到验签的完整闭环。

环境与依赖

  • Python 3.8+
  • 依赖库:cryptography(安装:pip install cryptography)

核心实现

以下代码实现完整流程:生成密钥对 → 对升级包二进制内容计算签名 → 验签并返回结果。签名使用SHA-256哈希,私钥签名、公钥验签。

import os
from cryptography.hazmat.primitives.asymmetric import ec, utils
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.exceptions import InvalidSignature

# ---------- 1. 生成密钥对(实际部署时由OTA服务端保存私钥,设备内置公钥) ----------
def generate_keypair():
    private_key = ec.generate_private_key(ec.SECP256R1())
    public_key = private_key.public_key()
    return private_key, public_key

# ---------- 2. 签名升级包 ----------
def sign_package(private_key, package_bytes: bytes) -> bytes:
    """返回DER格式签名"""
    signature = private_key.sign(
        package_bytes,
        ec.ECDSA(hashes.SHA256())
    )
    return signature

# ---------- 3. 验签升级包 ----------
def verify_package(public_key, package_bytes: bytes, signature: bytes) -> bool:
    """验签成功返回True,失败返回False"""
    try:
        public_key.verify(
            signature,
            package_bytes,
            ec.ECDSA(hashes.SHA256())
        )
        return True
    except InvalidSignature:
        return False

# ---------- 4. 密钥序列化(用于存储/分发) ----------
def serialize_keys(private_key, public_key):
    # 私钥PKCS8 PEM格式(OTA服务端保存)
    priv_pem = private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption()
    )
    # 公钥SubjectPublicKeyInfo PEM格式(烧录到设备)
    pub_pem = public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    )
    return priv_pem, pub_pem

# ---------- 5. 最小可运行闭环 ----------
if __name__ == "__main__":
    # 模拟升级包内容(实际为固件二进制)
    firmware = os.urandom(2048)  # 2KB随机数据模拟固件

    # 生成密钥
    priv, pub = generate_keypair()

    # 签名
    sig = sign_package(priv, firmware)
    print(f"签名长度: {len(sig)} 字节 (DER格式)")

    # 正常验签
    ok = verify_package(pub, firmware, sig)
    print(f"原始包验签结果: {ok}")

    # 篡改检测:修改1字节后验签
    tampered = bytearray(firmware)
    tampered[100] ^= 0xFF  # 翻转第100字节的每一位
    ok_tampered = verify_package(pub, bytes(tampered), sig)
    print(f"篡改包验签结果: {ok_tampered}")

    # 密钥序列化示例
    priv_pem, pub_pem = serialize_keys(priv, pub)
    print(f"私钥PEM行数: {len(priv_pem.splitlines())}")
    print(f"公钥PEM行数: {len(pub_pem.splitlines())}")

运行与验证

  1. 将上述代码保存为 ota_sign_verify.py。
  2. 运行:python ota_sign_verify.py
  3. 通过条件:输出中原始包验签结果: True且篡改包验签结果: False。这证明签名能识别任何字节级篡改。

OTA流程中的接入位置

  • 服务端:固件编译完成后调用sign_package(),将签名与固件打包下发。
  • 设备端:下载完成后,使用预置公钥调用verify_package(),验签通过才允许写入Flash。
  • 密钥管理:私钥仅在服务端;公钥在设备出厂时烧录,不可更新(或通过安全通道更新)。

常见问题

  • 验签失败:确认设备端公钥与服务端私钥是否同一对;确认签名传输过程中未截断。
  • 性能:ECDSA P-256验签在常见MCU(如Cortex-M4 @ 100MHz)上约需200~500ms,若需更快可改用Ed25519,但需确认硬件支持。
  • 防回滚:签名仅保证完整性,如需防版本回滚,需在固件中附加版本号并一并签名。