哈希算法基础:哈希函数特性与固件校验应用

聊固件完整性校验,绕不开哈希算法。说白了,哈希就是给数据算一个「指纹」——不管你的固件有多大,算出来都是一串固定长度的数字。这串数字变了,说明固件被人动过。

我刚开始做嵌入式安全那会儿,总觉得哈希就是个数学工具,用哪个都一样。后来踩过坑才明白,选错哈希算法,校验形同虚设。今天咱们就把哈希的基础掰开揉碎讲清楚。

哈希函数的三大特性

一个合格的哈希函数,必须满足三个核心特性。缺一个,就不能用在固件校验上。

  • 单向性:从哈希值反推原始数据,计算上不可行。你拿到一个 SHA-256 值,想还原出固件内容?算了吧,暴力破解都做不到。
  • 抗碰撞性:找不到两个不同的输入,却有相同的哈希值。如果存在碰撞,攻击者就能用恶意固件替换合法固件,哈希值还一样。
  • 确定性:同一输入,永远输出同一哈希值。这个好理解,不然校验就没法做了。

核心理解:单向性保证「算不出原文」,抗碰撞性保证「改不了内容」。固件校验主要依赖抗碰撞性——攻击者想伪造一个哈希值相同的恶意固件,理论上做不到。

嗯,这里要注意:确定性是基础,单向性是保障,抗碰撞性才是固件校验的命门。我在项目中见过有人用 CRC32 做固件校验,结果被攻击者轻松构造出碰撞——那场景,别提多尴尬了。

常见哈希算法对比

市面上哈希算法很多,但真正适合固件校验的就那么几个。我列个表,你一看就明白。

算法 输出长度 安全性 固件校验建议
MD5 128 位(16 字节) 已破解,可构造碰撞 不推荐,仅用于非安全场景
SHA-1 160 位(20 字节) 已破解,有实际碰撞攻击 不推荐,应尽快迁移
SHA-256 256 位(32 字节) 目前安全,无实际碰撞 推荐,固件校验首选
SHA-384 384 位(48 字节) 安全,但开销较大 资源充足时可选用
SHA-512 512 位(64 字节) 安全,计算开销高 高性能平台可选

避坑指南:我曾经接手过一个 IoT 项目,固件校验用的是 MD5。当时厂商说「MD5 够用,没人会攻击我们这种小设备」。结果呢?攻击者用公开的 MD5 碰撞工具,几分钟就构造出了哈希值相同的恶意固件。从那以后,我坚持所有固件校验至少用 SHA-256。

MD5 和 SHA-1 为什么不行了?

你可能会问:MD5 和 SHA-1 以前不是挺好吗?为什么现在不行了?

原因很简单——碰撞攻击被实现了。

  • MD5:2004 年,中国学者王小云团队提出了 MD5 碰撞攻击方法。现在用普通电脑,几分钟就能构造出两个不同文件但 MD5 值相同。固件校验用 MD5,等于没校验。
  • SHA-1:2017 年,Google 和 CWI 研究所联合发布了 SHAttered 攻击,成功构造出 SHA-1 碰撞。虽然成本较高,但对于国家级攻击者来说,完全可行。

说白了,这两个算法已经「退休」了。固件校验这种安全敏感场景,千万别再用。

SHA-256 为什么是当前首选?

SHA-256 属于 SHA-2 家族,输出 256 位哈希值。目前没有公开的碰撞攻击方法,安全性经过十几年验证。

我个人习惯在固件校验中优先选 SHA-256,原因有三:

  1. 安全性足够:256 位的输出空间,碰撞概率极低,实际攻击不可行。
  2. 计算效率适中:相比 SHA-384/512,SHA-256 在嵌入式 MCU 上也能跑得动。我曾在 Cortex-M4 上做过测试,1MB 固件计算 SHA-256 大约耗时 50ms,完全可以接受。
  3. 生态支持好:几乎所有安全芯片、TEE、BootROM 都原生支持 SHA-256 硬件加速。

小技巧:如果你的 MCU 支持硬件哈希加速器,一定要用。软件算 SHA-256 虽然也能跑,但功耗和耗时都会增加。我在一个电池供电的设备上,就因为用了软件哈希,导致启动时间多了 200ms——用户反馈开机太慢,后来切到硬件加速才解决。

哈希在固件校验中的典型应用

哈希在固件校验里怎么用?我给你画个流程图,一看就懂。

固件哈希校验流程 固件发布 原始固件二进制 计算哈希值 SHA-256(固件) 存储哈希值 安全存储区 固件更新 接收新固件 计算新哈希 SHA-256(新固件) 哈希比对 新哈希 == 存储哈希? 匹配 → 固件可信,允许更新 不匹配 → 拒绝更新,告警 说明: 哈希值必须存储在安全区域(如一次性可编程存储器或安全芯片内),防止被篡改。 如果攻击者能同时修改固件和存储的哈希值,校验就失去了意义。

流程其实很简单:发布固件时算一次哈希存起来,更新固件时再算一次哈希做比对。关键点在于——哈希值必须安全存储。我见过不少产品,哈希值直接放在 Flash 的普通区域,攻击者改固件的同时把哈希也改了,校验形同虚设。

实际代码示例:计算固件 SHA-256

下面是一个在嵌入式环境中计算固件 SHA-256 的伪代码示例。实际使用时,你需要根据 MCU 平台调用对应的硬件加速库。

// 伪代码:固件 SHA-256 校验
#include "sha256.h"

// 从安全存储区读取参考哈希值
uint8_t reference_hash[32];
read_from_secure_storage(reference_hash);

// 计算当前固件的 SHA-256
uint8_t computed_hash[32];
SHA256_CTX ctx;
sha256_init(&ctx);
sha256_update(&ctx, firmware_ptr, firmware_size);
sha256_final(&ctx, computed_hash);

// 比对哈希值
if (memcmp(reference_hash, computed_hash, 32) == 0) {
    // 固件可信,允许启动
    boot_firmware();
} else {
    // 固件被篡改,拒绝启动
    enter_safe_mode();
    report_error(ERROR_FIRMWARE_TAMPERED);
}

经验之谈:我建议在比对哈希时使用常量时间比较函数,防止时序攻击。标准库的 memcmp 在发现第一个不同字节就会返回,攻击者可以通过测量响应时间推断哈希值。用 timing_safe_memcmp() 或者自己写一个固定循环次数的比较函数。

哈希校验的局限性

哈希校验虽然好用,但不是万能的。你得清楚它的边界:

  • 哈希只校验完整性,不校验真实性:哈希能告诉你固件有没有被改过,但不能告诉你这个固件是不是来自合法发布者。攻击者可以自己编译一个恶意固件,算好哈希,替换掉你的固件和哈希值——校验照样通过。
  • 哈希值本身需要保护:如果哈希值存储在可被篡改的区域,攻击者可以同时替换固件和哈希值。

所以,哈希校验通常和数字签名配合使用。哈希保证完整性,数字签名保证真实性。这个我们后面章节会详细讲。

一句话总结:哈希是固件校验的基石,但单独使用不够。SHA-256 是当前最佳选择,MD5 和 SHA-1 已经退役。哈希值必须安全存储,比对要用常量时间函数。下一层,我们会在哈希基础上叠加数字签名,解决「谁签发的固件」这个问题。