安全启动和安全固件更新(SBSFU)3:加密基础

第2篇:加密基础 ------ 密码学概念速通


目录

  1. 为什么需要了解加密?
  2. 哈希函数(Hash)
  3. [对称加密(Symmetric Encryption)](#对称加密(Symmetric Encryption))
  4. [非对称加密(Asymmetric Encryption)](#非对称加密(Asymmetric Encryption))
  5. [数字签名(Digital Signature)完整流程](#数字签名(Digital Signature)完整流程)
  6. 本项目的加密方案全景
  7. 密钥管理基本概念
  8. 可选加密方案对比与选择

1. 为什么需要了解加密?

1.1 一个无法回避的事实

打开 SBSFU 的核心配置文件,你看到的第一行关键代码就是:

c 复制代码
// se_crypto_config.h 第72行
#define SECBOOT_CRYPTO_SCHEME SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384

这一行包含了四个密码学术语的组合:ECDSAAES256CBCSHA384

如果你不理解它们各自是干什么的、以及它们如何协作,你将无法:

  • 理解固件在传输和存储中受到了怎样的保护
  • 判断当前选择的加密方案是否适合自己的产品
  • 排查运行时出现的签名验证失败、解密错误等问题
  • 在进入生产模式时正确地管理和保护密钥

1.2 本篇的定位

你不需要成为密码学专家。本文的目标是用通俗的语言、生活中可以类比的例子、直观的 ASCII 图表,帮你建立对这几个概念的正确直觉。

记住一句话:密码学不是魔法,只是数学。你用钥匙开门,用指纹打卡------这些日常行为背后的逻辑,就是密码学的逻辑。


2. 哈希函数(Hash)

2.1 核心概念

哈希函数是一种"数据压缩器":它能把任意长度 的输入数据,压缩成一段固定长度的输出(称为摘要、哈希值、或指纹)。

复制代码
输入可以是任何东西:
  "Hello"                            → SHA-256 → 64位十六进制 (32字节)
  "Hello World, this is a long..."   → SHA-256 → 64位十六进制 (32字节)
  一部 4GB 的蓝光电影                 → SHA-256 → 64位十六进制 (32字节)

输入哪怕只改变 1 个 bit,输出就完全变样:
  "Hello World"                      → SHA-256 → 7F83B165...
  "Hello WorlD"  (只把d改成D!)       → SHA-256 → E65A4B3F...
                                          ↑ 完全不同!

2.2 四个关键特性(为什么哈希能做"指纹")

复制代码
特性 1: 确定性 (Deterministic)
  同一个输入,无论计算多少次,哈希值始终相同
  就像你的指纹:今天是这个纹路,明天还是这个纹路

特性 2: 单向性 (Pre-image Resistance)
  给定哈希值,无法反向计算出原始数据
  就像看到指纹,你无法反推出这个人的长相

特性 3: 抗碰撞性 (Collision Resistance)
  找不到两个不同的输入产生相同的哈希值
  就像没有两个人的指纹是完全相同的

特性 4: 雪崩效应 (Avalanche Effect)
  输入改变 1 个 bit,输出至少一半的 bit 会改变
  就像"张三"和"张四"虽然只差一个字,但指纹完全不同

2.3 生活类比:文件指纹

把哈希想象成文件的"指纹":

复制代码
  你:        身高 175cm, 体重 70kg, 黑头发, 棕色眼睛...
  指纹:      特定的纹路图案 (对任何人都唯一)

  文件A:     版本号=2.0, 代码=0x4801..., 配置=...
  哈希(SHA-384): a3f8c9d2... (48字节的"数字指纹")
  • 你看一个人的手指纹路,就能确定"这是谁"(身份识别)
  • 你算一个文件的 SHA-384,就能确定"这个文件是否被改过"(完整性校验)
  • 你无法从指纹反推这个人的身高体重(单向性)
  • 修改文件哪怕 1 个字节,SHA-384 结果就会面目全非(雪崩效应)

2.4 本项目中的哈希算法:SHA-384

参数 SHA-256 SHA-384 (本项目使用)
输出长度 256 位 = 32 字节 384 位 = 48 字节
安全强度 128 位 192 位
内部状态 256 位 512 位
块大小 512 位 (64 字节) 1024 位 (128 字节)
在 SBSFU 中的用途 ECDSA P-256 方案的完整性校验 ECDSA P-384 方案的完整性校验

为什么选 SHA-384 而不是 SHA-256?

这是一个"配套"选择。ECDSA P-384 签名方案需要搭配至少 384 位的哈希算法,才能发挥 P-384 曲线的完整安全强度(192 位)。如果 P-384 配 SHA-256,安全强度会降级到 128 位------相当于你买了一把超强的锁(P-384),却用了一根细铁丝当钥匙(SHA-256)。

2.5 在 SBSFU 中的实际应用

SBSFU 中的 FwTag 字段就是 SHA-384 的哈希值:

复制代码
编译时 (PC侧):
  固件二进制 (UserApp.bin)
      │
      └──→ [SHA-384] → FwTag (48字节)
           存入固件头部的 FwTag 字段

启动时 (设备侧):
  解密后得到的固件
      │
      └──→ [SHA-384] → 新计算的 FwTag'
  
  比较: FwTag == FwTag' ?
     ├── 相等 → 固件完整,未被篡改
     └── 不等 → 固件被修改过(哪怕只改了1位),拒绝执行!

2.6 动手试一试

python 复制代码
# Python 中计算文件的 SHA-384
import hashlib

with open("UserApp.bin", "rb") as f:
    data = f.read()

hash_obj = hashlib.sha384(data)
print(hash_obj.hexdigest())  # 输出 96 个十六进制字符 = 48 字节

# 或者用命令行:
# openssl dgst -sha384 UserApp.bin

3. 对称加密(Symmetric Encryption)

3.1 核心概念

对称加密使用同一把密钥进行加密和解密:

复制代码
加密过程:  明文数据 + 密钥 → [加密算法] → 密文 (看起来像乱码)
解密过程:  密文数据 + 密钥 → [解密算法] → 明文 (恢复原始数据)

3.2 生活类比:同一个钥匙开锁

想象一个带锁的箱子:

复制代码
  你有一把钥匙 (密钥)
  
  锁箱子 (加密):
    把文件放进箱子 → 用钥匙锁上 → 别人拿到箱子也打不开
  
  开箱子 (解密):
    把箱子拿到手 → 用同一把钥匙打开 → 取出文件
  
  关键问题: 钥匙不能丢,也不能被别人复制!
            谁有这把钥匙,谁就能打开箱子。

3.3 AES(Advanced Encryption Standard)

AES 是目前全球最广泛使用的对称加密标准,由美国国家标准与技术研究院(NIST)于 2001 年发布。它是经过全球密码学家多年公开竞赛和审查后选出的优胜算法。

AES 的核心参数:

复制代码
AES 加密的基本操作单元是"块"(Block),每个块固定为 128 位 (16 字节):

  ┌──────────────┐
  │ 16 字节明文块  │
  └──────┬───────┘
         │  + AES 密钥 (128/192/256 位)
         ▼
  ┌──────────────┐
  │ AES 加密变换   │  ← 多轮替换、移位、混合、轮密钥加
  └──────┬───────┘
         ▼
  ┌──────────────┐
  │ 16 字节密文块  │
  └──────────────┘

密钥长度对比:

AES 等级 密钥长度 加密轮数 安全强度 SBSFU 中的使用
AES-128 16 字节 (128 位) 10 轮 128 位 可选
AES-192 24 字节 (192 位) 12 轮 192 位 不常用
AES-256 32 字节 (256 位) 14 轮 256 位 本项目使用

为什么本项目选 AES-256?

  1. 与 ECC P-384 配套(192 位安全强度的非对称方案,搭配 256 位对称方案,整体不出现短板)
  2. 对量子计算攻击有一定抵抗力(Grover 算法将 256 位降为 128 位,仍有实用安全边界)
  3. 密钥长度增加一倍的存储开销(16→32 字节)在 STM32 上是完全可接受的

3.4 AES-CBC 模式

CBC(Cipher Block Chaining,密文块链接)是 AES 的一种工作模式,解决了"相同明文块产生相同密文块"的问题。

复制代码
AES-CBC 加密过程图解:

  明文块1         明文块2         明文块3
  (16字节)       (16字节)       (16字节)
    │              │              │
    │     ┌───────┘              │
    ▼     ▼                      ▼
  ┌────┐   密文块1           ┌────┐
  │XOR │ ◄─────────────────  │XOR │ ◄─────────────────
  └──┬─┘                     └──┬─┘
     │                          │
  ┌──┴──────┐              ┌──┴──────┐
  │AES 加密  │              │AES 加密  │
  │(密钥)    │              │(密钥)    │
  └──┬──────┘              └──┬──────┘
     │                          │
     ▼                          ▼
  密文块1                    密文块2
  (传给下一块XOR)           (传给下一块XOR)

IV(Initialization Vector,初始向量)的作用:

复制代码
IV = 16 字节随机数 (不需要保密,但必须不可预测)

如果没有 IV:
  同一密钥加密 "Hello" → 密文A
  同一密钥加密 "Hello" → 密文A  (相同!攻击者能发现重复内容)

有了 IV (每次不同):
  同一密钥 + IV1 加密 "Hello" → 密文A
  同一密钥 + IV2 加密 "Hello" → 密文B  (完全不同!)
  
  攻击者无法判断两个密文是否来自相同的明文

IV 在 SBSFU 中的存储位置 :IV 被存放在固件头部的 InitVector 字段(16 字节),随固件一起传输。设备收到后从头部提取 IV,用于 AES-CBC 解密。

3.5 AES-GCM 模式(对比参考)

虽然本项目使用 CBC 模式,但 SBSFU 也支持 GCM 模式:

特性 AES-CBC AES-GCM
加密
防篡改(认证) 否(需额外 MAC) 是(内建 GMAC)
并行处理 加密不能并行 加密和解密都能并行
实现复杂度 较低 较高
是否需要填充 是(PKCS#7) 否(CTR 模式变体)
SBSFU 中的状态 本项目使用 仅对称方案使用

CBC 模式下,明文必须填充到 16 字节的整数倍(PKCS#7 填充);GCM 模式下不需要填充,因为它本质上是流密码模式。

3.6 在 SBSFU 中的实际应用

复制代码
编译时 (PC侧,prepareimage.py):
  UserApp.bin (明文固件,例如 150KB)
      │
      ├──→ 生成随机 IV (16字节)
      │
      └──→ [AES-256-CBC 加密]
           密钥: 32字节 (预先生成,烧录在 SE Key Region)
           IV: 16字节 (随机生成,写入固件头部)
           输出: 加密后的 .sfu 数据

运行时 (设备侧,SE 内部):
  加密的 .sfu 数据
      │
      ├──→ 从固件头部读取 IV (16字节)
      │
      └──→ [AES-256-CBC 解密]
           密钥: 从 SE Key Region 加载 (PCROP保护)
           输出: 解密后的明文固件 (暂存在 RAM 中,不写回 Flash)

一个重要的实现细节:SBSFU 在启动时对固件进行"流式"解密------不是将整个 150KB 密文一次解密完,而是按块(16 字节或更小的 chunk)逐块解密。这样做的好处是:

  • RAM 占用小(不需要 150KB 的缓冲区,只需几个块大小的 buffer)
  • 可以边解密边写入 Active Slot 的 Flash 区域
  • 如果在解密过程中发现签名不匹配,可以提前终止,不浪费 Flash 擦写寿命

4. 非对称加密(Asymmetric Encryption)

4.1 核心概念

非对称加密使用一对数学上相关但不相同的密钥:

  • 私钥(Private Key):严格保密,只有你知道

  • 公钥(Public Key):可以公开给任何人

    核心性质:
    用私钥做的操作 → 只有对应公钥能验证
    用公钥做的操作 → 只有对应私钥能解密

    类比:
    私钥 = 你的个人印章(独一无二,只有你持有)
    公钥 = 你的印章样本(贴在公告栏上,任何人都可以核对)

    复制代码
    任何人拿到盖了你印章的文件,都可以对照公告栏上的样本来确认:
    "嗯,这个印章是真的,这份文件确实是你发出的。"
    
    但没有人能根据公告栏上印章的图案,反向雕刻出一模一样的印章。

4.2 生活类比:信箱投递

复制代码
非对称加密的经典类比:

  信箱投递场景:
  ┌───────────────────────────────────────────┐
  │                                          │
  │  大楼门口有一排信箱                        │
  │  每个信箱有一个投递口(公钥)               │
  │  和一个锁孔(私钥)                        │
  │                                          │
  │  任何人(公钥持有者)都可以:                │
  │    把信通过投递口塞进信箱                   │
  │    = 用公钥加密数据                        │
  │                                          │
  │  只有信箱主人(私钥持有者)可以:             │
  │    用钥匙打开信箱,取出信件                  │
  │    = 用私钥解密数据                        │
  │                                          │
  └───────────────────────────────────────────┘

4.3 ECC(Elliptic Curve Cryptography,椭圆曲线密码学)

ECC 是目前最先进、最高效的非对称加密方案。相比老一代的 RSA,ECC 提供:

对比维度 RSA-3072 ECDSA P-256 ECDSA P-384 (本项目)
等效安全强度 128 位 128 位 192 位
公钥大小 384 字节 64 字节 96 字节
私钥大小 ~384 字节 32 字节 48 字节
签名大小 384 字节 64 字节 96 字节
签名速度 (STM32F4) ~2000ms ~50ms ~120ms
适用场景 PC/服务器 IoT/手机 高安全 IoT/汽车

为什么嵌入式设备偏爱 ECC?

  • 密钥短 = Flash 占用小(嵌入式 Flash 寸土寸金)
  • 运算快 = 设备启动快(用户不希望每次开机等 2 秒做验签)
  • 功耗低 = 电池续航长(穿戴设备、传感器节点)

4.4 ECDSA(椭圆曲线数字签名算法)

ECDSA = ECC + DSA(基于椭圆曲线的数字签名算法)。这是本项目用于固件签名验证的核心算法。

签名过程(开发者侧,使用私钥):

复制代码
步骤 1: 计算要签名数据的哈希值
  固件头部数据 (版本号、大小、IV、FwTag等)
      │
      └──→ [SHA-384] → Hash = 48字节摘要

步骤 2: 用私钥对哈希值签名
  Hash (48字节) + ECDSA私钥 (48字节)
      │
      └──→ [ECDSA Sign 算法]
           内部使用 P-384 曲线参数
           生成一个随机数 k (每次签名都不同)
           进行椭圆曲线点乘运算
           输出: r (48字节) + s (48字节) = 签名 (96字节)

步骤 3: 将签名写入固件头部
  固件头部.HeaderSignature = 这 96 字节的签名

验证过程(设备侧,使用公钥):

复制代码
步骤 1: 从固件头部读取签名和待验证数据
  HeaderSignature (96字节)  ← 从固件头读出
  固件头部待验证部分         ← 从固件头读出

步骤 2: 重新计算哈希
  固件头部数据 (版本号、大小、IV、FwTag等)
      │
      └──→ [SHA-384] → Hash = 48字节

步骤 3: 用公钥验证签名
  Hash (48字节) + 签名 (96字节) + ECDSA公钥 (96字节)
      │
      └──→ [ECDSA Verify 算法]
           ├── 验证通过 → 签名有效,固件来源可信!
           └── 验证失败 → 签名无效,固件被伪造或篡改,拒绝!

4.5 曲线参数:P-256 vs P-384

参数 P-256 (secp256r1) P-384 (secp384r1, 本项目)
椭圆曲线方程 y^2 = x^3 - 3x + b (mod p) 同形式,参数更大
素数 p 的位宽 256 位 384 位
等效安全强度 128 位 192 位
私钥长度 32 字节 48 字节
公钥长度 64 字节 (未压缩) 96 字节 (未压缩)
ECDSA 签名长度 64 字节 (r+s) 96 字节 (r+s)
在 SBSFU 中的密钥 SE_SYMKEY_LEN=16 SE_SYMKEY_LEN=32
在 SBSFU 中的签名 SE_HEADER_SIGN_LEN=64 SE_HEADER_SIGN_LEN=96
在 SBSFU 中的公钥 SE_ASYM_PUBKEY_LEN=64 SE_ASYM_PUBKEY_LEN=96

这些值都定义在 se_def_metadata.h 中,由 SECBOOT_CRYPTO_SCHEME 宏自动选择:

c 复制代码
// se_def_metadata.h (根据加密方案自动选择参数)
#if (SECBOOT_CRYPTO_SCHEME == SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384)
  #define SE_SYMKEY_LEN        (32U)   // AES-256 密钥 = 32 字节
  #define SE_IV_LEN            (16U)   // AES-CBC IV = 16 字节
  #define SE_TAG_LEN           (48U)   // SHA-384 摘要 = 48 字节
  #define SE_HEADER_SIGN_LEN   (96U)   // ECDSA P-384 签名 = 96 字节
  #define SE_ASYM_PUBKEY_LEN   (96U)   // ECDSA P-384 公钥 = 96 字节
#endif

4.6 在 SBSFU 中的实际应用

复制代码
整体流程:

  开发者持有:               设备中存放:
  ┌──────────────┐       ┌──────────────┐
  │ ECDSA 私钥    │       │ ECDSA 公钥    │
  │ (48字节)     │       │ (96字节)     │
  │ 严格保密!     │       │ PCROP保护    │
  │ 绝对不能泄露  │       │ 烧录后不可改  │
  └──────────────┘       └──────────────┘
       │                       │
       │ 签名                  │ 验签
       ▼                       ▼
  prepareimage.py          SBSFU SE
  固件头部 + 私钥           固件头部 + 公钥
  → HeaderSignature        → 验证通过/失败

5. 数字签名(Digital Signature)完整流程

5.1 概念

数字签名 = 哈希 + 非对称加密的结合。它将"你认可这份数据的指纹"这一事实编码为一段无法伪造的签名数据。

数字签名同时提供了:

  • 认证性(Authenticity):证明数据确实来自声称的发送者(因为有私钥的人才能签名)
  • 完整性(Integrity):证明数据在传输过程中没有被修改(因为哈希值会暴露任何篡改)
  • 不可否认性(Non-repudiation):签名者不能事后否认自己签过这份数据

5.2 SBSFU 固件头签名流程(完整图解)

复制代码
═══════════════════════════════════════════════════════════════
               开发阶段:在 PC 上生成签名(prepareimage.py)
═══════════════════════════════════════════════════════════════

                    UserApp.bin (明文固件)
                         │
          ┌──────────────┼──────────────┐
          │              │              │
          ▼              ▼              ▼
    ① 生成IV       ② 计算FwTag      ③ AES加密固件
    随机16字节      SHA-384(固件)    AES-256-CBC
          │              │              │
          │         FwTag(48B)      加密固件(.sfu)
          │              │              │
          └──────────────┼──────────────┘
                         │
                         ▼
          ┌─────────────────────────────┐
          │  组装固件头 (FW Header)       │
          │                             │
          │  SFUMagic[4]    = "SFU1"   │
          │  ProtocolVersion             │
          │  FwVersion       = 1.0      │
          │  FwSize          = 150000   │
          │  PartialFwOffset = 0        │
          │  PartialFwSize   = 0        │
          │  FwTag[48]       = ←②      │
          │  PartialFwTag[48]= 0        │
          │  InitVector[16]  = ←①      │
          │  Reserved[4]     = 0        │
          └──────────────┬──────────────┘
                         │
                         │  待签名部分:
                         │  从 SFUMagic 到 Reserved
                         │  (总共 128 字节)
                         │
                         ▼
          ┌─────────────────────────────┐
          │  ④ 计算固件头哈希             │
          │  SHA-384(待签名部分)          │
          │  → HeaderHash (48字节)       │
          └──────────────┬──────────────┘
                         │
          ┌──────────────┴──────────────┐
          │  ⑤ ECDSA 签名               │
          │  ECDSA_Sign(HeaderHash,     │
          │             ECDSA_PrivateKey)│
          │  → HeaderSignature (96字节) │
          └──────────────┬──────────────┘
                         │
                         ▼
          ┌─────────────────────────────┐
          │  ⑥ 最终固件包 (.sfb)         │
          │                             │
          │  [FW Header (232字节)]       │
          │  其中包含:                   │
          │    待签名部分: 128 字节       │
          │    HeaderSignature: 96 字节  │
          │    FwImageState: 96 字节     │
          │    PrevHeaderFingerprint:32B │
          │                             │
          │  [加密固件数据 (可变长度)]     │
          │  使用 AES-256-CBC 加密       │
          └─────────────────────────────┘
                    =
              UserApp.sfb (通过 YMODEM 发送)


═══════════════════════════════════════════════════════════════
               运行阶段:在设备上验证签名 (SBSFU SE)
═══════════════════════════════════════════════════════════════

  收到 UserApp.sfb
       │
       ▼
  ┌─────────────────────────────┐
  │  ① 解析固件头                │
  │  读取 SFUMagic = "SFU1"     │
  │  读取所有头部字段            │
  │  提取 HeaderSignature       │
  └──────────────┬──────────────┘
                 │
       ┌─────────┴─────────┐
       │                   │
       ▼                   ▼
  ② 重新计算哈希        ③ 提取签名
  SHA-384(待签名部分)     HeaderSignature
  → HeaderHash' (48B)    (96字节)
       │                   │
       └─────────┬─────────┘
                 │
                 ▼
  ┌─────────────────────────────┐
  │  ④ ECDSA 验签               │
  │  ECDSA_Verify(HeaderHash',  │
  │               HeaderSignature,│
  │               ECDSA_PublicKey)│
  └──────────────┬──────────────┘
                 │
        ┌────────┴────────┐
        │                 │
        ▼                 ▼
    验签通过!          验签失败!
        │                 │
  ┌─────┴─────┐    ┌─────┴─────┐
  │ 继续解密   │    │ 拒绝固件   │
  │ 解密固件   │    │ 进入Loader │
  │ 完整性校验 │    │ 等待有效   │
  │ 安装运行   │    │ 固件       │
  └───────────┘    └───────────┘

5.3 为什么不能伪造签名?

复制代码
攻击者的困境:

  攻击者想: "我要造一个假固件,让设备接受它。"

  攻击者有的:
    - 公钥 (96字节) ------ 这是公开信息,他可以获得
    - 固件头部格式 ------ 这是确定的,他可以仿造

  攻击者没有的:
    - 私钥 (48字节) ------ 这被开发者严格保管

  攻击者可以:
    1. 自己生成一对新密钥 (私钥' + 公钥')
    2. 用私钥' 签名自己的恶意固件
    3. 把恶意固件发给设备

  设备端:
    设备里存的是开发者的公钥 (不是攻击者的公钥')
    用开发者的公钥去验证攻击者的签名 → 必定失败!
    
  因为:
    私钥' 签的名 → 只能由公钥' 验证
    公钥 要验证 → 必须是私钥签的名
    
    私钥' != 私钥 → 公钥' != 公钥 → 验签失败

  攻击者唯一的希望:
    从公钥反推出私钥 (椭圆曲线离散对数问题)
    → 这被数学证明在计算上是不可行的 (即使有超级计算机也需要数十亿年)

6. 本项目的加密方案全景

6.1 X-CUBE-CRYPTO:CAVP 认证的加密库

SBSFU 的底层加密操作不是自行实现的,而是调用 ST 官方的 X-CUBE-CRYPTO 加密库。这个库经过了 CAVP(Cryptographic Algorithm Validation Program,美国 NIST 密码算法验证计划)认证:

认证项目 认证机构 意义
CAVP 认证 NIST(美国国家标准与技术研究院) 证明加密算法实现与标准完全一致,无实现缺陷
涵盖算法 AES、SHA、ECDSA、DRBG、GCM、CCM 等 所有 SBSFU 使用的算法均已在认证范围内

这对你意味着什么?

  • 你不需要自己实现加密算法(自己写加密 = 几乎必然有漏洞)
  • 使用的算法实现经过了严格的第三方审计和标准符合性测试
  • 在需要安全认证的行业(医疗、汽车、支付)中,CAVP 认证是合规的基础

6.2 配置确认

c 复制代码
// se_crypto_config.h 第72行
#define SECBOOT_CRYPTO_SCHEME SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384

// 这个宏展开为值 5U (第78行)
#define SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384  (5U)

6.3 三层保护汇总

复制代码
┌─────────────────────────────────────────────────────────┐
│            SBSFU 加密方案:三层防护全景                    │
│                                                         │
│  ┌─────────────────────────────────────────────────┐    │
│  │  认证层 (Authentication)                         │    │
│  │  算法: ECDSA (Elliptic Curve DSA)              │    │
│  │  曲线: P-384 (secp384r1)                       │    │
│  │  密钥: 私钥48字节(开发者) / 公钥96字节(设备)     │    │
│  │  签名: 96字节 (r + s)                           │    │
│  │  保护对象: 固件头部 (版本、大小、IV、FwTag等)    │    │
│  │  证明: "这个固件确实是我们发布的"                │    │
│  └─────────────────────────────────────────────────┘    │
│                         +                               │
│  ┌─────────────────────────────────────────────────┐    │
│  │  加密层 (Encryption)                             │    │
│  │  算法: AES-256-CBC                              │    │
│  │  密钥: 32字节 (对称密钥)                         │    │
│  │  初始向量: 16字节 (IV, 随机生成)                 │    │
│  │  工作模式: CBC (Cipher Block Chaining)          │    │
│  │  填充方式: PKCS#7                                │    │
│  │  保护对象: 固件代码+数据本体                     │    │
│  │  保证: "固件内容不会被第三方读取"                │    │
│  └─────────────────────────────────────────────────┘    │
│                         +                               │
│  ┌─────────────────────────────────────────────────┐    │
│  │  完整性层 (Integrity)                            │    │
│  │  算法: SHA-384                                  │    │
│  │  摘要: 48字节 (384位)                           │    │
│  │  对比: 解密后的固件重新计算SHA-384 vs FwTag      │    │
│  │  保护对象: 解密后的完整固件内容                   │    │
│  │  保证: "固件在传输/存储中未被修改哪怕1位"        │    │
│  └─────────────────────────────────────────────────┘    │
│                                                         │
│  总安全强度: 192 位 (由 ECDSA P-384 决定)              │
│  固件头总大小: 232 字节 (128认证 + 96签名 + 96状态)   │
│                                                         │
└─────────────────────────────────────────────────────────┘

6.4 数据流全景图

复制代码
  开发者 PC (prepareimage.py)          STM32G474RE 设备 (SBSFU SE)
  ═══════════════════════════          ══════════════════════════

  UserApp.bin (明文)                   收到 UserApp.sfb
      │                                      │
      ├── SHA-384 → FwTag ──────────────┐    ├── 解析固件头
      │                                  │    │   提取 HeaderSignature
      ├── AES-256-CBC 加密 ──┐           │    │   提取 IV
      │    ← AES_Key (32B)   │           │    │   提取 FwTag
      │    ← IV (16B随机)     │           │    │
      │                      │           │    ├── ECDSA 验签 ◄──┘
      │                      │           │    │   ← ECC_PublicKey
      │                      │           │    │   验证通过? 
      │                      │           │    │   ├─否 → 拒绝
      ├── 组装头部 ───────────┤           │    │   └─是 ↓
      │   {版本, 大小,        │           │    │
      │    FwTag, IV, ...}    │           │    ├── AES-256-CBC 解密 ◄──┘
      │        │              │           │    │   ← AES_Key + IV
      │        ▼              │           │    │   解密后明文(暂存RAM)
      │   SHA-384(头部)       │           │    │
      │        │              │           │    ├── SHA-384(解密后固件)
      │        ▼              │           │    │   → FwTag'
      │   ECDSA_Sign          │           │    │
      │   ← ECC_PrivateKey    │           │    ├── FwTag' == FwTag? ◄──┘
      │        │              │           │    │   ├─否 → 拒绝
      │        ▼              │           │    │   └─是 ↓
      │   HeaderSignature ────┤           │    │
      │        │              │           │    └── 固件有效 → Swap安装
      │        ▼              ▼           │
      └──→ UserApp.sfb = [头部+签名] + [加密固件] ──→ 通过YMODEM发送

7. 密钥管理基本概念

7.1 密钥不能以明文存放

这是安全设计的第一原则:密钥永远不能以明文形式静态存储在 Flash 中

复制代码
如果密钥以明文方式存放:

  地址 0x08000400: 01 23 45 67 89 AB CD EF ...  (AES-256 密钥的32字节)
  
  攻击者只需:
    1. 用调试器连接设备
    2. 读取 0x08000400 开始的 32 字节
    3. 获得了完整的 AES 密钥
    4. → 可以解密所有使用此密钥的固件!

7.2 多层密钥保护机制

SBSFU 使用"纵深防御"策略,通过层次化的硬件保护来确保密钥安全:

复制代码
┌──────────────────────────────────────────────────────┐
│                 密钥保护层次结构                       │
│                                                      │
│  ┌────────────────────────────────────────────────┐  │
│  │  第一层: 密钥混淆 (Obfuscation)                 │  │
│  │  · 密钥不是连续存储的 32/96 字节                │  │
│  │  · 密钥被拆散编码在 MOVW/MOVT 汇编指令的立即数中  │  │
│  │  · 静态分析只能看到分散的汇编指令, 看不到完整密钥 │  │
│  │  · 只有 CPU 执行这些指令时, 密钥才在寄存器中重组  │  │
│  │  ┌──────────────────────────────────────────┐   │  │
│  │  │  第二层: PCROP 保护 (硬件级)              │   │  │
│  │  │  · STM32G4 的专有代码读保护机制           │   │  │
│  │  │  · CPU 可以从该区域取指令执行              │   │  │
│  │  │  · CPU 不能以"数据读"方式访问该区域        │   │  │
│  │  │  · 调试器 (ST-LINK/J-LINK) 完全不能访问    │   │  │
│  │  │  · 即使 RDP Level 1 被绕过, PCROP 仍有效  │   │  │
│  │  │  · 最小保护粒度: 512 字节 (本项目正好)     │   │  │
│  │  │  ┌────────────────────────────────────┐   │  │  │
│  │  │  │  第三层: MPU 隔离 (运行时)          │   │  │  │
│  │  │  │  · 用户程序运行在非特权模式          │   │  │  │
│  │  │  │  · SE 区域被 MPU 标记为特权访问      │   │  │  │
│  │  │  │  · 用户程序尝试访问 SE 区域 → 触发   │   │  │  │
│  │  │  │    MemManage Fault (内存管理异常)    │   │  │  │
│  │  │  │  · 只能通过 CallGate 间接调用 SE 服务│   │  │  │
│  │  │  │  ┌──────────────────────────────┐  │   │  │  │
│  │  │  │  │  密钥数据本身                  │  │   │  │  │
│  │  │  │  │  · AES-256 密钥 (32字节)     │  │   │  │  │
│  │  │  │  │  · ECDSA P-384 公钥 (96字节) │  │   │  │  │
│  │  │  │  └──────────────────────────────┘  │   │  │  │
│  │  │  └────────────────────────────────────┘   │  │  │
│  │  └──────────────────────────────────────────┘   │  │
│  └────────────────────────────────────────────────┘  │
│                                                      │
│  即使攻击者:                                          │
│    - 打开了芯片封装 (物理攻击)                         │
│    - 绕过了 RDP Level 1 (读保护)                      │
│    - 仍然无法从 PCROP 区域读取密钥数据                 │
│    - PCROP 是芯片硬件层的保护, 很难被非侵入式攻击突破  │
└──────────────────────────────────────────────────────┘

7.3 安全世界 vs 普通世界(隔离模型)

复制代码
┌─────────────────────────────────────────────────────────┐
│                    STM32G474RE                          │
│                                                         │
│  ┌───────────────────────┐  ┌───────────────────────┐  │
│  │   安全世界 (Secure)    │  │  普通世界 (Non-Secure) │  │
│  │                       │  │                       │  │
│  │  · SE 代码 + 密钥     │  │  · UserApp 用户程序   │  │
│  │  · MPU 特权模式       │  │  · MPU 非特权模式     │  │
│  │  · 可以访问全部内存    │  │  · 不能访问 SE 区域   │  │
│  │  · 提供加密服务       │  │  · 调用 SE 必须经过   │  │
│  │                       │  │    CallGate 入口      │  │
│  │    ┌─────────┐        │  │                       │  │
│  │    │ 密钥    │        │  │    UserApp             │  │
│  │    │ PCROP   │        │  │        │               │  │
│  │    └─────────┘        │  │   需要签名验证?        │  │
│  │         ↑             │  │        │               │  │
│  │    ┌────┴────┐        │  │   调用 CallGate ──────>│  │
│  │    │CallGate │◄───────┼──── 传递参数和请求        │  │
│  │    │入口     │────────┼──> 返回结果               │  │
│  │    └─────────┘        │  │                       │  │
│  │                       │  │                       │  │
│  └───────────────────────┘  └───────────────────────┘  │
│                                                         │
│  隔离机制:                                              │
│    - MPU 在安全世界入口配置 SE 区域为特权访问             │
│    - 用户程序尝试直接访问 0x08000400 → MemManage Fault  │
│    - CallGate 是安全世界暴露给普通世界的唯一合法入口       │
└─────────────────────────────────────────────────────────┘

7.4 生产环境中的签名:HSM 集成流程

在开发阶段,私钥可能就存放在你的 PC 上,prepareimage.py 直接读取并签名。但在生产环境中,这绝对不行!

为什么不能用 PC 直接签名?

  • PC 可能被恶意软件感染,私钥有泄露风险
  • 开发人员离职时可能带走私钥
  • 无法审计"谁在什么时候签了什么固件"
  • 不符合行业安全合规要求(如 ISO 27001、SOC 2)

HSM(Hardware Security Module,硬件安全模块) 是生产签名的标准方案:

复制代码
HSM 集成签名流程:

  ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
  │ prepareimage.py │     │   HSM Server    │     │   签名服务器      │
  │ (PC/CI服务器)    │     │   或签名服务      │     │                 │
  │                 │     │                 │     │                 │
  │ ① 生成未签名头部 │     │                 │     │                 │
  │   (Unsigned     │     │                 │     │                 │
  │    Header.bin)  │     │                 │     │                 │
  │        │        │     │                 │     │                 │
  │        └────────┼────>│ ② 发送未签名头部  │     │                 │
  │                 │     │   进行审批流程     │     │                 │
  │                 │     │        │         │     │                 │
  │                 │     │        ▼         │     │                 │
  │                 │     │ ③ HSM 内部签名    │     │                 │
  │                 │     │   ECDSA_Sign(    │     │                 │
  │                 │     │     HeaderHash,  │     │                 │
  │                 │     │     PrivateKey)  │     │ ← 私钥永不离HSM │
  │                 │     │        │         │     │                 │
  │                 │     │        ▼         │     │                 │
  │                 │     │ ④ 返回签名        │     │                 │
  │  ⑤ 合并签名到头部│◄────┼─── Signature.bin │     │                 │
  │  ⑥ 打包 .sfb    │     │                 │     │                 │
  └─────────────────┘     └─────────────────┘     └─────────────────┘

关键原则:
  · 私钥在 HSM 中生成 → 永不以明文形式离开 HSM
  · 签名操作在 HSM 内部完成 → PC/服务器永远接触不到私钥
  · 每次签名都有审计日志 → 可追溯
  · HSM 需要多人授权才能操作 → 防止单点作恶

prepareimage.py 支持分离式签名流程:

  1. 通过 keygen 命令生成密钥对(私钥可注入 HSM,公钥写入设备固件)
  2. 通过 pack 命令生成未签名的固件头部(--nosign 参数)
  3. 将未签名头部发送给 HSM 进行签名
  4. 将 HSM 返回的签名合并到 .sfb 文件中

具体的命令行操作将在第 14 篇(密钥生成与固件打包)中详细演示。


8. 可选加密方案对比与选择

8.1 SBSFU 支持的加密方案全集

SBSFU 提供了 5 种预定义的加密方案,在 se_crypto_config.h 中定义:

c 复制代码
#define SECBOOT_ECCDSA_WITHOUT_ENCRYPT_SHA256   (1U)  // 只签名,不加密
#define SECBOOT_ECCDSA_WITH_AES128_CBC_SHA256   (2U)  // 签名 + 加密(128位)
#define SECBOOT_AES128_GCM_AES128_GCM_AES128_GCM (3U)  // 纯AES-GCM方案
#define SECBOOT_ECCDSA_WITHOUT_ENCRYPT_SHA384   (4U)  // 只签名,SHA384
#define SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384   (5U)  // 完整方案(本项目)

8.2 各方案对比

方案 认证方式 加密 完整性 Flash开销 签名大小 固件头大小 适用场景
方案1 ECDSA P-256 SHA-256 最小 64B 192B 固件不需保密,只需防篡改
方案2 ECDSA P-256 AES-128-CBC SHA-256 中等 64B 192B 消费级IoT(平衡安全与性能)
方案3 AES-GCM AES-128-GCM AES-GCM 中等 16B 192B 纯对称方案,密钥管理简单
方案5 (本项目) ECDSA P-384 AES-256-CBC SHA-384 较大 96B 232B 高安全IoT、工业、汽车
方案4 ECDSA P-384 SHA-384 较大 96B 232B 高安全但不需加密的场景

8.3 如何选择适合你的方案?

复制代码
决策树:

  你的固件需要保密吗? (防止竞争对手逆向)
    ├── 不需要 → 固件不需要加密
    │   ├── 安全要求一般 → 方案1 (ECDSA P-256 + SHA-256 + 不加密)
    │   └── 安全要求较高 → 方案4 (ECDSA P-384 + SHA-384 + 不加密)
    │
    └── 需要! → 固件必须加密
        ├── 密钥管理希望简单 → 方案3 (纯AES-GCM方案)
        │   注: 对称方案的缺点是如果密钥泄露, 所有设备全部沦陷
        │      非对称方案即使公钥泄露, 私钥还在你手里
        │
        ├── 需要非对称 + 一般安全 → 方案2 (ECDSA P-256 + AES-128 + SHA-256)
        │   适用: 消费级智能家居、穿戴设备、玩具
        │
        └── 需要非对称 + 高安全 → 方案5 (ECDSA P-384 + AES-256 + SHA-384)
            适用: 工业控制、汽车电子、医疗设备、支付终端
            ← 本项目的选择

8.4 方案名的解读技巧

SBSFU 的加密方案命名规则非常直观:

复制代码
SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384
   │       │      │     │    │    │
   │       │      │     │    │    └── 完整性算法: SHA-384
   │       │      │     │    └─────── 加密工作模式: CBC
   │       │      │     └──────────── 加密算法: AES-256
   │       │      └────────────────── 有加密 (WITH = 带加密)
   │       └───────────────────────── 认证算法: ECDSA (非对称)
   └───────────────────────────────── 安全启动标识

SECBOOT_AES128_GCM_AES128_GCM_AES128_GCM
   │       │    │    │    │    │
   │       │    │    │    │    └── 完整性: AES-GCM TAG
   │       │    │    │    └─────── 加密: AES-128-GCM
   │       │    │    └──────────── 认证: AES-128-GCM TAG (对称!)
   │       │    └───────────────── 三次出现AES-GCM = 三个操作都用AES-GCM
   │       └────────────────────── 对称加密方案
   └────────────────────────────── 安全启动标识

小结

本文帮你建立了 SBSFU 涉及的核心密码学概念。以下是关键要点:

复制代码
你的"密码学速查卡":

  ┌──────────────┬──────────────────┬──────────────────────┐
  │   想达到的效果  │    使用的技术      │   在 SBSFU 中的体现    │
  ├──────────────┼──────────────────┼──────────────────────┤
  │ 确认固件来自官方│ ECDSA P-384 签名  │ HeaderSignature(96B) │
  │ 固件内容不泄露  │ AES-256-CBC 加密  │ .sfu 加密固件本体     │
  │ 固件未被篡改    │ SHA-384 哈希校验  │ FwTag(48B) 完整性标签 │
  │ 密钥不被提取    │ PCROP + MPU 隔离  │ SE Key Region 保护    │
  └──────────────┴──────────────────┴──────────────────────┘

记忆口诀:
  - 哈希 = 文件指纹 (相同内容→相同指纹, 改了内容→指纹不同)
  - 对称加密 = 同一把钥匙开锁 (加密和解密用同一密钥)
  - 非对称 = 信箱投递 (公钥投递/验证, 私钥开箱/签名)
  - 数字签名 = 哈希 + 非对称 (先算指纹再签字, 公钥验签确认)
  - 本项目 = ECC P-384 + AES-256-CBC + SHA-384 (三重防护)

下一篇:第4篇:内存布局 ------ SBSFU 的 Flash 与 RAM 分配详解

相关推荐
Paranoid-up1 小时前
安全启动和安全固件更新(SBSFU)2:环境搭建
安全·iap·安全启动·安全升级·sbsfu
是wzoi的一名用户啊~1 小时前
Floyd 模版 弗洛伊德算法 模版
c++·算法·动态规划·图论·floyd
昵称小白1 小时前
图论专题(下)
算法·图论
懒惰的coder1 小时前
MPC算法
算法
余俊晖1 小时前
图文混合文档的轻量级多模态listwise重排框架:Rank-Nexus
人工智能·算法·机器学习
桌面运维家1 小时前
服务器异常登录日志排查方法与安全防护实战
运维·服务器·安全
小许同学记录成长1 小时前
三维编辑功能实现
qt·算法·无人机
平行侠1 小时前
026FFT快速乘法 - 从信号处理到大数计算的革命
数据结构·算法·信号处理
晓梦林1 小时前
Fuzzz靶场学习笔记
笔记·学习·安全·web安全