
什么是哈希函数?
密码学哈希函数接收任意输入并产生固定长度的输出(“哈希”或“摘要”)。它具有三个核心属性:
- 确定性:相同输入总是产生相同输出
- 单向性:无法从哈希反推出输入
- 雪崩效应:输入微小变化会导致输出完全改变
SHA-256("hello") → 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA-256("Hello") → 185f8db32921bd46d35cc2e13b6a97d7ffc4bce5c5ffbcab3b0b4f7de59e8d7 (完全不同!)

主要哈希算法
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(非面向用户的安全)、一些非关键校验和。

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 亿次

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-256 或 SHA-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 和其他哈希。