MD5、SHA-1、SHA-256 怎么选:哈希不是加密,一次说清

哈希是单向摘要,没有密钥就解不开;加密有密钥且可逆,Base64 是编码。讲清 MD5 还剩哪些合法用途、HMAC 防的到底是什么、为什么比较哈希要定长比较,以及文件校验最容易踩的换行与编码坑。

约 7 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证

先把三个东西分清:哈希、加密、编码

哈希是把任意长度数据映射成固定长度摘要的单向函数:没有密钥、没有反推算法,SHA-256 的输出找不回原文。它的用途是判断「是否相同」和「是否被改过」,不是保密。加密是用密钥的可逆变换,密钥丢了数据也回不来,所以凡是「以后还要读出来」的内容——手机号、银行卡、用户资料——才走加密。Base64 是编码,无密钥、任何人一秒还原,只是把字节换成能安全穿过 URL 和 JSON 的字符。三者的混淆是绝大多数安全问题的源头:把 Base64 当加密、把哈希当加密、把密码放哈希算法里。

概念对照表

手段可逆吗有密钥吗典型用途
哈希(MD5 / SHA-x)不可逆没有完整性校验、去重键、签名摘要
HMAC不可逆有(对称密钥)接口签名、webhook 校验、消息认证
数字签名(RSA / ECDSA)不可逆有(私钥签名、公钥验证)证书、代码签名、跨组织验证
对称加密(AES-GCM)可逆有(同一把钥匙)存储需要读回的数据
非对称加密(RSA-OAEP)可逆有(公钥加密、私钥解密)交换小体积密钥、点对点机密
Base64 / URL 编码可逆没有传输表示、放进 JSON 与 URL
压缩(gzip / zstd)可逆没有减小体积,可与加密叠加

MD5 现在还剩哪些能用场景

MD5 不是彻底作废,而是被限制在「碰撞不会造成伤害」的场景里。仍然合理:文件内容去重键、非安全性的缓存标识、遗留系统之间比对旧校验和(两边都只认 MD5 时)。已经不该用:任何密码存储、任何需要抗伪造的签名、SSL 证书指纹(浏览器早已拒绝 SHA-1 签发的证书)、以及任何"防篡改"宣传。SHA-1 同理——2017 年公开的碰撞构造只差几小时就能落地,现在只应作为兼容旧数据读取存在。真正的新项目一律 SHA-256 起步,需要抗量子视野再考虑 SHA-512 或 BLAKE2 系列。

算法参数对照

算法输出长度相对速度碰撞现状现在该用吗
MD5128 bit极快可构造碰撞仅兼容与去重
SHA-1160 bit快已构造碰撞仅兼容旧数据
SHA-224 / 256224 / 256 bit中无实际碰撞默认选择
SHA-384 / 512384 / 512 bit较慢无实际碰撞需要更宽裕时
HMAC-SHA256256 bit(带密钥)中依赖底层算法接口签名首选
SHA-3 / BLAKE2256 bit 起中偏快无实际碰撞新协议与高吞吐场景
命令行算摘要并比对
# 文本:注意末尾换行会进哈希,换个写法结果就变
printf "hello" | sha256sum
# 5d41402abc4b2a76b9719d911017c592...

echo "hello" | sha256sum
# 2cf24dba5fb0a30e26e83b2ac5b9e29e...   ← 多了 \n,摘要不同

# 文件必须用二进制模式比对
sha256sum dist/app.bin > app.sha256
sha256sum -c app.sha256
Node:HMAC 签名与验签
const crypto = require("crypto");

function hmacHex(secret, body) {
  return crypto.createHmac("sha256", secret).update(body, "utf8").digest("hex");
}

const secret = process.env.WEBHOOK_SECRET;
const body = req.rawBody;                // 用原始字节,别用解析后的对象重新序列化
const sig = req.get("X-Signature");

// 定长比较,避免逐字节试探
const a = Buffer.from(hmacHex(secret, body), "hex");
const b = Buffer.from(sig, "hex");
const valid = a.length === b.length && crypto.timingSafeEqual(a, b);

HMAC 存在的理由:长度延展攻击

直觉写法是「把密钥拼在消息前面再哈希」:H(key || message)。它有一个致命缺陷——已知 H(key||m) 和 m 的长度,攻击者能在不知道 key 的情况下算出 H(key || m || padding || 追加内容),从而伪造出一条看起来合法的消息。SHA-1 的实际碰撞就是利用这类结构弱点实现的。HMAC 不是简单地把 key 换个位置,而是做两次哈希:先用 key 构造内外两层填充,再把 message 从中间喂进去,于是原始的迭代结构被打断,延展出来的结果不再有效。这就是接口签名、Webhook 校验、TOTP 都用 HMAC 而不是裸哈希的原因。

选型五问

  1. 以后需要读回原文吗?需要 → 加密(AES-GCM),不要用哈希。
  2. 需要防篡改还是防抵赖?防篡改且双方共享一把密钥 → HMAC;需要第三方公证、能区分签名人 → 非对称数字签名。
  3. 对面能拿到什么?对手只能读摘要 → 哈希够;对手能自选输入 → 必须用带密钥的 HMAC。
  4. 存的是密码吗?是 → 换 bcrypt / Argon2id,本文其余算法都不适用。
  5. 只是排障对个文件?那就明说是非安全用途,用 SHA-256,别把摘要字段命名成 signature。

五个常见误区

  • 「MD5 只是慢一点」——它快了一到两个数量级,这才是问题所在。
  • 「SHA-256 是加密」——不是,数据库被读走时它对攻击者毫无阻碍。
  • 「Base64 编码过了就安全」——没有密钥的东西不需要解密,散列出来即可还原。
  • 「用哈希值前 8 位当短 ID」——32 位空间在几十万数据量下就会大面积碰撞。
  • 「比较摘要用 == 就够了」——字符串比较会在首个差异处提前返回,用定长比较函数。

文件校验对不上的四个常见原因

按这个顺序查,八成能定位。一、换行符:Windows 用 CRLF、Linux/macOS 用 LF,文本文件在两端算出的摘要不同;二、编码:BOM 有无、UTF-8 与 GBK 都影响字节序列;三、模式:命令行默认按文本模式读取,Windows 上会做换行转换,校验二进制文件要显式指定二进制模式或改用工具的「文件输入」;四、下载源:镜像站重新打包、CDN 就地压缩、Git 的 autocrlf 设置都会让「同一个文件」产生不同字节。先比较文件大小,再比较摘要,能省掉一半的无效排查。

常见问题

MD5 还能用吗?

能用的地方很窄:文件去重键、非安全性的缓存标识、和只认 MD5 的遗留系统对接。密码、签名、证书指纹、防篡改校验这四类一律别用。确实需要读回原文的场景走加密,需要防伪造走 HMAC 或数字签名。

哈希和加密到底有什么区别?

哈希是单向的,没有密钥,找不回原文,用来判断数据是否被改过;加密是带密钥的可逆变换,加密方和授权的解密方都能读回明文。密码只存哈希,因为没人需要读回密码;手机号、银行卡这类以后还要显示的字段才存加密结果。

为什么验证密码不用 SHA-256,偏要用 bcrypt?

SHA-256 设计目标是快,普通硬件每秒几十亿次,攻击者拿到哈希后离线穷举几乎不花时间;bcrypt 和 Argon2 是刻意设计得慢的,参数存在哈希串里,以后还能调高。核心差别不是安全性等级,而是「让每猜一次都变贵」。

HMAC 比「密钥拼在消息里再哈希」安全在哪?

简单写法 H(key||message) 存在长度延展攻击:攻击者无需知道密钥就能在原消息后追加内容并算出合法摘要。HMAC 做了两层带密钥的填充再哈希,破坏了这个结构,让延展结果失效。接口签名、Webhook 校验、TOTP 都因此用 HMAC。

📂

松开以自动格式化

支持 .json / .sql / .csv / .html / .css / .xml / .yaml 文件