密码学基础回顾:哈希、对称与非对称加密

各位同学好,我是你们的讲师。在正式进入固件防篡改之前,我习惯先花一节课把密码学的基础打牢。你可能会问:“这些算法网上资料一大堆,为什么还要讲?”

嗯,我在做固件安全审计时发现,很多漏洞其实不是算法本身的问题,而是开发者对算法特性的理解有偏差。比如有人用SHA256做完整性校验,却忘了加盐;有人用RSA加密小数据,结果性能惨不忍睹。说白了,工具用错了地方。

这一章,我们就聚焦三个核心工具:哈希函数、对称加密、非对称加密。我会结合项目中的实际坑点,帮你建立正确的“算法直觉”。

本章核心脉络:从“单向性”到“保密性”再到“身份认证”,理解每种算法在固件安全中的角色。

密码学基础核心概念关系图 密码学三大核心工具 固件安全基础 哈希函数 SHA256 对称加密 AES 非对称加密 RSA / ECC 单向性 · 防篡改 高速 · 密钥共享 身份认证 · 密钥分发 三者组合 = 固件防篡改 + 证书链校验

2.1 哈希函数:固件的“数字指纹”

哈希函数,说白了就是一个单向的“榨汁机”。你把固件镜像丢进去,出来一个固定长度的摘要。但反过来,你没法从摘要还原出原始固件。这个特性,在固件防篡改中至关重要。

我个人最常用的哈希算法是SHA256。为什么不是MD5或SHA1?MD5已经被证明存在碰撞攻击,SHA1也在逐渐被淘汰。我在一次客户现场就遇到过:对方用MD5做固件完整性校验,结果攻击者构造了一个碰撞镜像,绕过了校验。嗯,从那以后,我所有项目都强制要求SHA256起步。

避坑指南:我曾经在OTA升级方案中只对固件本体做了SHA256,却忘了对元数据(版本号、设备ID)做哈希。结果攻击者篡改了版本号,导致设备回滚到有漏洞的旧固件。记住:哈希的对象要覆盖所有关键字段。

SHA256的输出是256位,也就是32字节。在固件中,我们通常把它编码为64个十六进制字符,或者直接用二进制存储。来看一个简单的计算示例:

// 伪代码:计算固件镜像的SHA256
uint8_t firmware[] = { ... };  // 固件数据
uint8_t hash[32];              // 存放摘要

sha256_calculate(firmware, sizeof(firmware), hash);

// 输出:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
// (这是空字符串的SHA256,仅作示例)

哈希函数还有一个重要特性:雪崩效应。输入哪怕只改一个比特,输出的摘要也会面目全非。这个特性让攻击者无法通过微调固件来保持哈希值不变。

特性 说明 固件安全中的应用
单向性 无法从摘要反推原文 保护固件敏感信息
抗碰撞性 极难找到两个不同输入产生相同摘要 防止固件被替换
雪崩效应 输入微小变化导致输出巨大差异 检测固件任何篡改
固定输出长度 无论输入多大,输出都是32字节 便于存储和比较

2.2 对称加密:AES 的实战选择

对称加密,就是加密和解密用同一把钥匙。你想想看,这就像你用一把锁锁住箱子,收件人用同样的钥匙打开。在固件领域,对称加密常用于加密固件镜像本身,防止被逆向分析。

AES是目前的主流选择。它有三种密钥长度:128、192、256位。我建议在固件场景中直接用AES-256。为什么?虽然AES-128在数学上已经足够安全,但多出来的几微秒加密时间,在固件升级场景中完全可以接受。我曾经优化过一个项目,把AES-128换成AES-256,解密时间只增加了不到5%,但安全性提升了一个量级。

重要提醒:AES的安全性高度依赖密钥管理。我曾经见过一个设备,把AES密钥硬编码在固件里,攻击者用binwalk一提取就拿到了。密钥必须存储在安全元件(SE)或TEE中,至少也要做白盒加密处理。

AES有几种工作模式,我列个表格对比一下:

模式 特点 固件场景推荐度
ECB 简单,但相同明文块产生相同密文块 ❌ 不推荐
CBC 需要IV,密文块链式依赖 ✅ 可用,注意IV管理
GCM 同时提供加密和完整性校验 ✅ 强烈推荐
CTR 可并行,适合流式处理 ✅ 适合大固件

我个人习惯在固件加密中使用AES-256-GCM。为什么?因为它同时提供了加密和认证,能检测密文是否被篡改。你想想看,如果只用CBC模式,攻击者虽然解不出明文,但可以翻转密文比特来破坏固件。GCM模式通过附加的认证标签,能发现这种篡改。

// 伪代码:AES-256-GCM 加密固件
uint8_t key[32];      // 256位密钥
uint8_t iv[12];       // 96位初始向量
uint8_t firmware[];   // 固件明文
uint8_t ciphertext[]; // 密文输出
uint8_t tag[16];      // 认证标签

aes_gcm_encrypt(key, iv, firmware, sizeof(firmware), ciphertext, tag);

// 解密时验证tag,如果不匹配则拒绝加载

2.3 非对称加密:RSA 与 ECC 的选择

非对称加密,用一对密钥:公钥加密,私钥解密。或者反过来,私钥签名,公钥验签。在固件防篡改中,我们主要用它的签名验签能力。

RSA和ECC是两大主流。我直接说结论:新项目优先选ECC。为什么?

  • 密钥长度:RSA 2048位 vs ECC 256位,安全性相当。在固件中,256位的公钥比2048位好存储得多。
  • 性能:ECC的签名速度略慢于RSA,但验签速度快得多。固件升级场景中,设备端验签是瓶颈,ECC优势明显。
  • 资源消耗:ECC对内存和CPU的要求更低,适合嵌入式设备。

我在一个物联网项目中,设备只有Cortex-M4内核,128KB RAM。用RSA 2048做验签,一次操作要占用近20KB的临时缓冲区,差点把内存撑爆。换成ECC P-256后,缓冲区降到不到1KB,验签时间也从2秒降到200毫秒。嗯,这个对比很直观吧?

核心概念:非对称加密在固件安全中的典型用法是——私钥签名固件,公钥验签。公钥可以公开,但必须保证其完整性(通常由证书链保护)。私钥必须严格保密,存储在离线环境中。

来看一个签名验签的流程示例:

// 签名端(生产环境,私钥安全存储)
uint8_t private_key[];  // ECC私钥
uint8_t firmware[];     // 固件
uint8_t signature[64];  // ECDSA签名输出

ecdsa_sign(private_key, firmware, sizeof(firmware), signature);

// 验签端(设备端,公钥固件中)
uint8_t public_key[];   // ECC公钥
uint8_t firmware[];     // 收到的固件
uint8_t signature[64];  // 收到的签名

if (ecdsa_verify(public_key, firmware, sizeof(firmware), signature) == 0) {
    // 验签通过,固件可信
    flash_write(firmware);
} else {
    // 验签失败,拒绝升级
    error_handler();
}

实战建议:我曾经遇到一个坑——设备验签时只校验了固件主体,没校验固件头部的元数据(如版本号、设备类型)。攻击者把签名合法的旧版本固件头替换到新固件上,实现了降级攻击。记住:签名对象必须覆盖所有关键字段,或者把元数据也纳入签名范围。

2.4 三种算法的协同工作

在实际的固件防篡改方案中,这三种算法不是孤立使用的。我习惯把它们组合成一个“安全链条”:

  1. 哈希函数:对固件计算SHA256摘要,作为完整性校验的基础。
  2. 非对称加密:用私钥对固件的哈希值进行签名,设备端用公钥验签,确认固件来源可信。
  3. 对称加密:用AES加密固件本体,防止传输和存储过程中被逆向分析。

你想想看,如果只用哈希,攻击者可以替换固件并重新计算哈希。如果只用非对称加密,性能开销太大。如果只用对称加密,密钥分发是个难题。三者结合,才能构建一个完整的防护体系。