3. 密码学基础回顾:哈希函数、非对称加密与数字签名

各位同学,咱们今天聊点硬核的。安全启动的核心,说白了就是一套密码学协议。你如果不把哈希、非对称加密、数字签名这几个东西搞明白,后面看安全启动的代码就跟看天书一样。

我当年刚接触这块时,也踩过不少坑。有一次把哈希值和签名搞混了,结果在验证镜像时死活过不去,查了两天才发现是概念理解错了。所以今天咱们把这几个基础概念彻底捋清楚。

3.1 哈希函数:数据的「指纹」

哈希函数,你可以把它想象成给数据按个手印。不管输入多大,输出都是固定长度。而且只要数据改一个比特,哈希值就完全变了。

在安全启动里,我们最常用的是 SHA-256。它输出 256 位,也就是 32 个字节。

SHA-256 的核心特性:

  • 单向性:从哈希值反推原始数据,理论上不可能。我见过有人想暴力破解,算到天荒地老也算不出来。
  • 抗碰撞性:找到两个不同数据但哈希值相同,几乎不可能。嗯,这里要注意,理论上存在碰撞,但概率低到可以忽略。
  • 雪崩效应:输入改一点点,输出天翻地覆。

举个例子,你写个简单的 C 代码算一下:

#include <stdio.h>
#include <string.h>
#include <openssl/sha.h>

void print_hash(unsigned char *hash) {
    for(int i = 0; i < SHA256_DIGEST_LENGTH; i++)
        printf("%02x", hash[i]);
    printf("\n");
}

int main() {
    unsigned char data[] = "Hello, Bootloader!";
    unsigned char hash[SHA256_DIGEST_LENGTH];
    
    SHA256(data, strlen((char*)data), hash);
    print_hash(hash);
    
    // 改一个字符试试
    data[0] = 'h';
    SHA256(data, strlen((char*)data), hash);
    print_hash(hash);
    
    return 0;
}

输出结果会完全不一样。这就是为什么我们在验证固件完整性时,先算哈希再比对——只要哈希对得上,数据就没被篡改过。

我的经验:在 Bootloader 里实现 SHA-256 时,注意内存对齐。有些 MCU 对非对齐访问会触发异常,我吃过这个亏。

3.2 非对称加密:公钥与私钥

对称加密就像一把钥匙开一把锁,加密解密用同一个密钥。但问题来了——你怎么安全地把密钥传给对方?

非对称加密解决了这个问题。它用一对密钥:公钥公开,私钥自己藏好。公钥加密的数据只能用私钥解密,反过来私钥加密的数据只能用公钥解密。

在安全启动里,我们主要用两种算法:

算法 密钥长度 安全性基础 签名速度 典型应用
RSA 2048 / 4096 位 大整数分解 较慢 传统 Bootloader
ECC 256 / 384 位 椭圆曲线离散对数 较快 资源受限设备

你想想看,为什么 ECC 用更短的密钥能达到和 RSA 同等的安全强度?因为它的数学基础更复杂。256 位的 ECC 密钥,安全性和 3072 位的 RSA 差不多。

避坑指南:我曾经在一个项目里用了 1024 位的 RSA,后来发现已经被破解了。现在行业标准是至少 2048 位,我建议直接用 4096 位,反正 Bootloader 里签名验证只做一次,慢点没关系。

3.3 数字签名:证明「你是谁」和「数据没被改」

数字签名把哈希和非对称加密结合起来了。流程是这样的:

  1. 对固件算 SHA-256 哈希值
  2. 用私钥对哈希值加密,得到签名
  3. 把签名和固件一起发布
  4. 验证时,用公钥解密签名得到哈希值 A
  5. 对收到的固件算哈希值 B
  6. 比较 A 和 B,一致则通过

说白了,签名就是「用私钥加密的哈希值」。它同时保证了:

  • 完整性:哈希值对得上,数据没被改
  • 真实性:能用公钥解开,说明是持有私钥的人签的
  • 不可否认性:签名者无法抵赖

下面这张图把整个流程串起来了:

数字签名生成与验证流程 签名生成(发布者) 固件镜像 SHA-256 哈希 私钥加密 (签名) 固件 + 签名 (发布到设备) 签名验证(设备端) 接收固件 + 签名 SHA-256 哈希 公钥解密签名 (得到哈希值) 比较两个哈希值 (A == B ?) 验证通过 ✅ (启动固件) ❌ 说明:签名验证通过后,Bootloader 才认为固件是可信的。 任何一步失败,直接拒绝启动,进入恢复模式。

3.4 在 Bootloader 中的实际应用

好了,理论讲完了。咱们看看在 Bootloader 里具体怎么用。

典型的流程是这样的:

  1. 芯片上电,执行 ROM 里的 BootROM 代码
  2. BootROM 读取 Flash 中的 Bootloader 第一级(SPL)
  3. 用烧录在芯片 OTP 里的公钥验证 SPL 的签名
  4. 验证通过后,跳转到 SPL 执行
  5. SPL 再验证下一级 Bootloader 或 OS 内核

这就是所谓的「信任链」——每一级都验证下一级,环环相扣。

关键点:信任的根(Root of Trust)必须是硬件不可修改的。公钥要么烧在 OTP 里,要么用硬件唯一密钥派生。我见过有人把公钥放在 Flash 里,结果被攻击者替换了——那整个安全体系就形同虚设了。

3.5 常见误区与避坑

最后,我总结几个实际项目中容易踩的坑:

  • 哈希碰撞攻击:虽然 SHA-256 很安全,但别用 MD5 或 SHA-1,它们已经被攻破了。
  • 密钥管理:私钥一定要离线保存,最好用 HSM。我曾经见过有人把私钥放在 Git 仓库里...后果可想而知。
  • 侧信道攻击:RSA 和 ECC 的实现要注意时序攻击。简单的比较操作要用常量时间比较,否则攻击者能通过测量时间推断出密钥。
  • 随机数质量:生成密钥对时,随机数生成器一定要够随机。嵌入式设备上,硬件随机数生成器(TRNG)是必须的。

我的建议:如果你刚开始做安全启动,先用 RSA-2048 + SHA-256 的组合。它成熟、工具链完善、文档多。等把流程跑通了,再考虑换成 ECC 来节省空间和提升性能。