第2篇:加密基础 ------ 密码学概念速通
目录
- 为什么需要了解加密?
- 哈希函数(Hash)
- [对称加密(Symmetric Encryption)](#对称加密(Symmetric Encryption))
- [非对称加密(Asymmetric Encryption)](#非对称加密(Asymmetric Encryption))
- [数字签名(Digital Signature)完整流程](#数字签名(Digital Signature)完整流程)
- 本项目的加密方案全景
- 密钥管理基本概念
- 可选加密方案对比与选择
1. 为什么需要了解加密?
1.1 一个无法回避的事实
打开 SBSFU 的核心配置文件,你看到的第一行关键代码就是:
c
// se_crypto_config.h 第72行
#define SECBOOT_CRYPTO_SCHEME SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384
这一行包含了四个密码学术语的组合:ECDSA 、AES256 、CBC 、SHA384。
如果你不理解它们各自是干什么的、以及它们如何协作,你将无法:
- 理解固件在传输和存储中受到了怎样的保护
- 判断当前选择的加密方案是否适合自己的产品
- 排查运行时出现的签名验证失败、解密错误等问题
- 在进入生产模式时正确地管理和保护密钥
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?
- 与 ECC P-384 配套(192 位安全强度的非对称方案,搭配 256 位对称方案,整体不出现短板)
- 对量子计算攻击有一定抵抗力(Grover 算法将 256 位降为 128 位,仍有实用安全边界)
- 密钥长度增加一倍的存储开销(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 支持分离式签名流程:
- 通过
keygen命令生成密钥对(私钥可注入 HSM,公钥写入设备固件) - 通过
pack命令生成未签名的固件头部(--nosign参数) - 将未签名头部发送给 HSM 进行签名
- 将 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 分配详解