哈希算法入门:MD5、SHA-1、SHA-256的区别,哈希在签名中的作用

聊固件签名,绕不开哈希。说白了,哈希就是个「数字指纹」。你给它一段数据,它吐出一串固定长度的字符串。哪怕你改了一个 bit,这串字符串就变得面目全非。

我刚开始做固件时,觉得哈希就是个校验工具,跟签名关系不大。直到有一次,我在联调时发现签名验证总失败,折腾了两天,最后发现是哈希算法选错了。嗯,从那以后我再也不敢小看这一步了。

哈希算法的三个核心特性

不管 MD5 还是 SHA-256,它们都满足三个基本要求:

  • 单向性:从哈希值反推原始数据,理论上不可能。你想想看,如果哈希能逆回去,那签名还有什么意义?
  • 抗碰撞性:很难找到两个不同的输入,产生相同的哈希值。碰撞概率越低,算法越安全。
  • 雪崩效应:输入改一个 bit,输出大约一半的 bit 会翻转。这个特性在签名中特别重要——哪怕固件被篡改了一个字节,签名验证立刻就能发现。

核心理解:哈希不是加密。加密可以解密,哈希不行。哈希是「摘要」,不是「密文」。

MD5、SHA-1、SHA-256 的区别

这三个算法,我猜你肯定都听过。但它们的区别,很多人其实没搞清楚。我直接给你一张对比表,一目了然:

算法 输出长度 安全性 速度 当前状态
MD5 128 bit (16 字节) 极低,已破解 最快 不推荐用于签名
SHA-1 160 bit (20 字节) 低,有理论碰撞攻击 较快 逐步淘汰中
SHA-256 256 bit (32 字节) 高,目前安全 较慢 行业标准

避坑指南:我曾经在一个老项目里看到有人用 MD5 做固件签名校验。当时觉得「反正只是校验完整性,又不用防攻击」。结果客户反馈说固件被篡改后签名验证居然通过了……后来查出来是 MD5 碰撞攻击。从那以后,我所有新项目一律用 SHA-256。

为什么 MD5 和 SHA-1 不安全了?

MD5 的碰撞攻击早在 2004 年就被公开了。什么意思?就是攻击者可以构造两个不同的文件,但它们的 MD5 值一模一样。你想想看,如果签名用的是 MD5,攻击者完全可以替换你的固件,但签名验证还是通过。

SHA-1 的情况好一点,但也好不到哪去。2017 年 Google 和 CWI 研究所联合公布了 SHA-1 的碰撞攻击(SHAttered 攻击)。他们用了 6500 年的 CPU 计算时间,成功构造了两个不同的 PDF 文件,SHA-1 值完全相同。

我个人习惯是:只要涉及安全,直接上 SHA-256。别纠结性能,现在 MCU 的算力跑 SHA-256 完全没问题。

哈希在签名中的作用

签名过程其实分两步:先哈希,再签名。为什么不能直接对固件签名?因为非对称加密(RSA、ECDSA 等)处理大文件很慢。你想想看,一个几 MB 的固件,如果用私钥直接加密,那速度慢得没法用。

所以实际做法是:

  1. 对固件计算哈希值(比如 SHA-256)
  2. 对哈希值进行签名(用私钥加密)
  3. 把签名附加到固件后面

验证时反过来:

  1. 提取固件,重新计算哈希值
  2. 用公钥解密签名,得到原始哈希值
  3. 对比两个哈希值是否一致

我的经验:联调时最容易出问题的地方就是哈希计算的范围不一致。比如签名时算了整个固件,但验证时只算了固件主体(没算签名本身)。这种 bug 很难查,因为代码逻辑看起来都对。我建议你在设计阶段就把「哈希范围」明确写进接口文档里。

哈希算法在固件签名中的选择建议

  • 新项目:一律用 SHA-256。这是目前最稳妥的选择。
  • 兼容旧设备:如果旧设备只支持 SHA-1,可以考虑双哈希(SHA-1 + SHA-256),但我不推荐长期这么做。
  • 资源极度受限:比如只有几 KB RAM 的 MCU,可以考虑 SHA-224(SHA-256 的截断版本),但别用 MD5。

一张图看懂哈希在签名中的位置

下面这张图展示了哈希在固件签名和验证流程中的核心位置。我建议你把它存下来,联调时对照着看,能少走很多弯路。

哈希在固件签名与验证中的位置 签名过程(开发/发布端) 原始固件 SHA-256 哈希计算 私钥签名(RSA/ECDSA) 固件 + 签名打包 验证过程(设备端) 接收固件 + 签名 重新计算哈希值 公钥解密签名 对比两个哈希值 一致 → 验证通过 核心逻辑:哈希将任意长度的固件「压缩」为固定长度的摘要,签名只对摘要操作,大幅提升效率 验证时重新计算哈希并与解密后的哈希对比,确保固件未被篡改

实际联调中的哈希坑

联调时,哈希相关的坑我踩过不少。给你列几个最常见的:

  • 字节序问题:PC 端是大端序,有些 MCU 是小端序。哈希计算时如果字节序没对齐,结果肯定不一样。我建议在接口文档里明确写明「哈希计算采用大端序」。
  • 填充问题:有些哈希库在计算时会自动填充,有些不会。如果你手动拼接数据,一定要确认填充方式一致。
  • 签名范围:签名时到底算了哪些数据?是固件主体?还是固件 + 元数据?还是固件 + 签名本身(这个不对)?我建议在固件头部加一个字段,明确标识签名范围。

我的习惯:每次联调前,我会先在 PC 端用 Python 的 hashlib 库算一遍哈希,然后在 MCU 端用同样的数据算一遍,对比结果。如果一致,再往下走签名流程。这一步能过滤掉 80% 的哈希相关问题。

小结

哈希是固件签名的基石。MD5 和 SHA-1 已经不安全了,新项目直接上 SHA-256。记住:哈希不是加密,它是「数字指纹」。签名时先哈希再签名,验证时先哈希再对比。这个流程搞清楚了,联调时能省不少时间。