3、安全固件更新(SOTA/FOTA):更新包加密与签名、回滚保护机制、Vector 的更新客户端实现

固件更新,说白了就是给车机“打补丁”。但车不是手机,更新包要是被篡改,轻则功能失效,重则刹车失灵。我参与过好几个量产项目,每次聊到 OTA 安全,团队都会吵上几轮。今天咱们就把它拆开揉碎了讲清楚。

3.1 更新包加密与签名:双重保险

更新包在传输过程中,可能被截获、篡改,甚至被替换成恶意固件。怎么防?两个手段:加密签名

  • 加密:保证机密性。别人拿到包也解不开。
  • 签名:保证完整性和真实性。确保包是官方发的,没被改过。

我习惯的做法是:先签名,再加密。为什么?你想想看,如果先加密再签名,签名验证时你得先解密,流程上多了一步,而且解密密钥一旦泄露,签名就形同虚设。

核心流程:

  1. 生成固件镜像的哈希值(比如 SHA-256)。
  2. 用私钥对哈希值签名,生成签名数据。
  3. 将固件镜像 + 签名数据打包。
  4. 用对称密钥(AES-256)加密整个包。
  5. 用接收方的公钥加密对称密钥(RSA 或 ECC)。

嗯,这里要注意:私钥必须硬件保护。我在一个项目里见过把私钥硬编码在代码里的,结果被逆向出来,整个 OTA 系统都得重做。那叫一个惨。

3.2 回滚保护机制:别让旧版本害了你

回滚攻击是什么?攻击者把车机固件降级到一个有已知漏洞的旧版本,然后利用漏洞入侵。这招很阴,但也很常见。

怎么防?核心思路就一句话:让旧版本无法被安装

具体做法有两种:

方法 原理 我踩过的坑
版本号检查 Bootloader 比较当前版本和新版本的版本号。新版本必须 ≥ 当前版本。 版本号用字符串比较?坑死你。一定要用整数,且单调递增。
安全计数器 硬件中维护一个只增不减的计数器。每次更新成功,计数器 +1。Bootloader 检查新固件的计数器值必须 ≥ 当前值。 计数器溢出怎么办?我建议用 64 位,这辈子都够用。

警告: 我曾经遇到过一个项目,回滚保护只检查了版本号,但没检查签名。结果攻击者把旧固件重新签名后刷了进去。记住:回滚保护必须和签名验证联动。旧固件的签名必须被吊销,否则一切白搭。

3.3 Vector 的更新客户端实现

Vector 的工具链里,更新客户端通常跑在 vFlash 或者 CANoe 的测试环境里。但真正量产时,客户端是嵌入在 ECU 中的。我拿 vFlash 的架构来举例,因为它最典型。

客户端主要干三件事:

  • 接收更新包:通过 CAN、以太网或 FlexRay 接收。
  • 验证包:解密、验签、检查版本号。
  • 刷写固件:擦除 Flash、写入新固件、校验完整性。

代码逻辑大致如下(伪代码):

// 伪代码:更新客户端核心流程
int update_firmware(uint8_t* encrypted_package, uint32_t size) {
    // 1. 解密
    uint8_t* decrypted = decrypt_package(encrypted_package, size);
    if (decrypted == NULL) return ERROR_DECRYPT_FAIL;

    // 2. 验签
    if (!verify_signature(decrypted)) {
        free(decrypted);
        return ERROR_SIGNATURE_INVALID;
    }

    // 3. 检查回滚
    uint32_t new_version = get_version_from_package(decrypted);
    uint32_t current_version = read_current_version();
    if (new_version < current_version) {
        free(decrypted);
        return ERROR_ROLLBACK_DETECTED;
    }

    // 4. 刷写
    if (flash_write(decrypted) != OK) {
        free(decrypted);
        return ERROR_FLASH_FAIL;
    }

    // 5. 更新安全计数器
    increment_security_counter();

    free(decrypted);
    return OK;
}

你看,逻辑不复杂,但每一步都不能出错。我见过一个案例,解密步骤放在验签之前,结果攻击者用无效包把解密模块搞崩溃了。所以顺序很重要:先验签,再解密,能省掉很多不必要的计算,也安全。

小技巧: Vector 的 vFlash 支持 增量更新。只传输差异部分,能省带宽。但增量更新的回滚保护更复杂,因为旧版本和新版本之间可能有多个中间版本。我建议在增量场景下,强制要求全量更新作为基线,否则回滚保护容易出漏洞。

最后说一句:安全固件更新不是一锤子买卖。你得考虑密钥轮换、证书吊销、异常处理。我在一个项目里,就因为没处理好更新中断后的恢复逻辑,导致 ECU 变砖。嗯,从那以后,我每次都会在 Bootloader 里留一个“救援模式”,哪怕主固件刷坏了,还能通过 CAN 刷回来。

说白了,安全是设计出来的,不是测试出来的。你设计时多花一小时,产线上就能少熬一个通宵。