正在加载,请稍候…

哈希函数详解:MD5 vs SHA-256 vs bcrypt — 你应该用哪个?

了解密码学哈希函数的区别。学习何时使用 MD5、SHA-1、SHA-256 和 bcrypt,以及为什么用错哈希函数是一个严重的安全错误。

哈希函数详解:MD5 vs SHA-256 vs bcrypt — 你应该用哪个?

什么是哈希函数?

密码学哈希函数接收任意输入并产生固定长度的输出(“哈希”或“摘要”)。它具有三个核心属性:

  1. 确定性:相同输入总是产生相同输出
  2. 单向性:无法从哈希反推出输入
  3. 雪崩效应:输入微小变化会导致输出完全改变
SHA-256("hello")  → 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA-256("Hello")  → 185f8db32921bd46d35cc2e13b6a97d7ffc4bce5c5ffbcab3b0b4f7de59e8d7 (完全不同!)

哈希函数详解:MD5 vs SHA-256 vs bcrypt — 你应该用哪个? 插图

主要哈希算法

MD5(消息摘要算法 5)

  • 输出:128 位(32 个十六进制字符)
  • 速度:非常快
  • 安全性已破解——1996 年发现碰撞漏洞,2004 年实际可破解

不要将 MD5 用于任何安全目的。 攻击者可以构造两个不同文件但具有相同 MD5 哈希。彩虹表数据库覆盖了数十亿个常见密码。

仍可用于:碰撞抵抗不重要的校验和(验证下载是否因网络错误而损坏——而非攻击者导致)、非安全场景的文件去重。

SHA-1(安全哈希算法 1)

  • 输出:160 位(40 个十六进制字符)
  • 速度:快
  • 安全性已弃用——2017 年首次展示实际碰撞(Google 的 SHAttered 攻击)

大多数证书颁发机构在 2016 年停止颁发 SHA-1 证书。Git 内部仍使用 SHA-1 作为对象 ID(带有缓解措施),并正在迁移到 SHA-256。

仍可用于:遗留 Git 对象 ID(非面向用户的安全)、一些非关键校验和。

哈希函数详解:MD5 vs SHA-256 vs bcrypt — 你应该用哪个? 插图

SHA-256 / SHA-2 系列

  • 输出:SHA-256 为 256 位(64 个十六进制字符);还有 SHA-384、SHA-512
  • 速度:快(现代 CPU 上有硬件加速)
  • 安全性——无已知实际攻击

SHA-256 是当前行业标准,用于:

  • 数字签名(SSL/TLS 证书)
  • HMAC 消息认证
  • 数据完整性验证
  • 区块链(比特币使用 SHA-256)
  • 需要安全性的文件校验和
// Node.js
const crypto = require('crypto');
const hash = crypto.createHash('sha256').update('hello').digest('hex');
console.log(hash); // 2cf24dba5fb0a...

SHA-3 / Keccak

  • 输出:可变(SHA3-256、SHA3-512 等)
  • 速度:软件中比 SHA-2 慢
  • 安全性——内部结构与 SHA-2 完全不同

SHA-3 于 2015 年标准化,作为 SHA-2 发现弱点时的后备方案。如今用于以太坊和一些特殊应用。SHA-2 仍更广泛使用。

为什么 SHA-256 不适合密码

这是本文最重要的一点:

SHA-256 是一种快速哈希。这是它在数据完整性方面的优势——也是它在密码方面的致命缺陷。

现代 GPU 每秒可计算 100 亿次以上 SHA-256 哈希。攻击者窃取你的哈希密码数据库后,可以在几分钟内尝试数十亿个候选密码。

SHA-256("password123") → ef92b778bafe771207... 
// 现代 GPU 每秒可计算 100 亿次

哈希函数详解:MD5 vs SHA-256 vs bcrypt — 你应该用哪个? 插图

bcrypt — 专为密码设计

bcrypt 是一种密码哈希函数,设计于 1999 年,内置了故意的慢速:

  • 工作因子(成本):控制迭代次数的参数——成本加倍 ≈ 计算时间加倍
  • 内置盐:随机盐被纳入哈希,抵御彩虹表
  • 输出包含盐和成本:哈希字符串自包含
const bcrypt = require('bcrypt');

// 哈希密码(成本因子 12 ≈ 250ms)
const hash = await bcrypt.hash('myPassword123', 12);
// → '$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/...'

// 验证
const match = await bcrypt.compare('myPassword123', hash);
// → true

在成本因子 12 下,bcrypt 每次哈希约需 250ms。攻击者使用相同 GPU 每秒只能尝试约 4 次哈希——比 SHA-256 慢数个数量级。

Argon2 — 现代冠军

Argon2 在 2015 年密码哈希竞赛中获胜,是目前新系统的推荐选择:

  • 内存硬:需要大量 RAM,抵御 GPU/ASIC 攻击
  • 三种变体:Argon2i(抗侧信道)、Argon2d(抗 GPU)、Argon2id(推荐——混合型)
const argon2 = require('argon2');

const hash = await argon2.hash('myPassword', {
  type: argon2.argon2id,
  memoryCost: 65536,  // 64 MB
  timeCost: 3,
  parallelism: 4,
});

决策指南

使用场景 推荐算法
密码存储 Argon2id(新系统)或 bcrypt(成熟方案)
数据完整性 / 文件校验和 SHA-256
HMAC 认证 SHA-256SHA-512
TLS/SSL 证书 SHA-256
数字签名 SHA-256
非安全文件去重 MD5(快速,可接受)
正在审计的遗留系统 升级到 SHA-256 或更高

加盐 — 为什么重要

如果没有盐,两个用户密码相同则哈希相同。单个预计算彩虹表可一次性破解所有密码。

是在哈希前添加到密码中的随机值:

hash = SHA256(salt + password)

bcrypt 和 Argon2 自动处理加盐。如果你对密码使用 SHA-256(不要这样做),你必须自己添加盐并将其与哈希一起存储。

常见问题

问:存储密码的 SHA-256 哈希安全吗? 不安全。SHA-256 太快了。请改用 bcrypt 或 Argon2id。

问:我可以使用 MD5 检查文件下载吗? 对于防止意外损坏(而非篡改),可以。对于安全敏感的验证(例如验证软件下载未被恶意软件替换),请使用 SHA-256。

问:如果密码已经以 MD5 存储,我需要验证怎么办? 在下次成功登录时,使用 bcrypt 重新哈希:先验证 MD5,然后重新保存 bcrypt 哈希并删除 MD5。

问:我应该多久更新一次 bcrypt 成本因子? 每 2-3 年重新评估一次,因为硬件性能在提升。在生产服务器上,哈希计算至少应耗时 100ms。

→ 使用 哈希文本工具 即时计算 SHA-256 和其他哈希。