哈希生成器

MD5 和 SHA-1 为何已被攻破 — 以及"被攻破"的真正含义

MD5 由 Ron Rivest 于 1991 年设计。SHA-1 由 NSA 于 1995 年设计。两者都是通用密码哈希函数,都生成固定长度的摘要,并且多年来都被认为已"被攻破"。但"被攻破"一词需要明确定义,因为它涵盖了两种截然不同的攻击。 碰撞攻击是指攻击者能够找到两个不同的输入 A 和 B,使它们产生相同的哈希值。第二原像攻击是指攻击者在给定固定输入 A 的情况下,能够找到哈希到相同值的另一个输入 B。后者的难度大得多。MD5 自 2004 年起碰撞即被攻破,如今在一台笔记本电脑上几分钟就能生成实际可用的碰撞。SHA-1 的第一个实际碰撞由 Google 的"SHAttered"攻击于 2017 年 2 月演示 — 他们生成了两个具有相同 SHA-1 哈希的不同 PDF 文件,成本约为 6,500 CPU 年和 11 万美元的 AWS GPU 计算,2020 年的"Shambles"论文将其降至约 4.5 万美元。MD5 和 SHA-1 目前都还没有实际可行的第二原像攻击,但差距逐年缩小,而"我们无法给攻击者一个固定目标"是一个不宜依赖的脆弱属性。 实际影响:停止将 MD5 或 SHA-1 用于任何安全目的。现实世界中的事件并非纸上谈兵。Flame 恶意软件(2012 年,据信为国家行为者所为)滥用了 Microsoft Terminal Services 证书签名链中的 MD5 碰撞,伪造了一张有效的 Windows 代码签名证书。Git 在 2.13 过渡计划之前一直使用 SHA-1 进行内容寻址,而 SHAttered 演示的构造正是为了表明两个不同的提交可以共享同一个哈希 — Git 于 2017 年引入了碰撞检测,并正在向 SHA-256 迁移。CA/Browser Forum 于 2017 年强制 TLS 证书弃用 SHA-1。PGP、S/MIME 以及大多数代码签名也随之跟进。 那么 MD5 和 SHA-1 在什么场景下仍可接受?非对抗性的完整性校验。如果你计算哈希是为了检测随机损坏(CDN 文件与源站的对比、下载的产物与公布的校验和的对比),且攻击者没有伪造碰撞的动机,MD5 和 SHA-1 仍然可用。在抗碰撞性不属于安全模型的数据结构内部,它们作为指纹也依然合适 — Git 的 SHA-1 在 2026 年之所以基本还能存活,是因为 Git 的威胁模型假定生产者是可信的,而内容寻址只是一种便利手段。 现代推荐选择:任何新系统使用 SHA-256 或 SHA-3-256,需要高吞吐量时以 BLAKE3 作为快速的现代替代方案。SHA-256 自 2001 年以来一直稳固,没有出现有意义的削弱;密码学界有信心再对它托付 20 年以上的信任。SHA-3(Keccak,2015 年标准化)在结构上与 SHA-2 不同,因此即使 SHA-2 被发现缺陷,SHA-3 也不太可能受到同样的影响。BLAKE3 在软件实现中比 SHA-256 快约 5 倍,安全级别相同,非常适合超大文件。
// MD5 collision in seconds (e.g., HashClash, fastcoll)
fastcoll input.bin a.bin b.bin
md5sum a.bin b.bin
# 008ee33a9d58b51cfeb425b0959121c9  a.bin
# 008ee33a9d58b51cfeb425b0959121c9  b.bin   <-- different files, same MD5

// Modern hashing in Node 20+
import { createHash } from 'node:crypto';
createHash('sha256').update(buf).digest('hex');     // recommended
createHash('sha3-256').update(buf).digest('hex');   // SHA-3 (Keccak)
createHash('blake2b512').update(buf).digest('hex'); // BLAKE2

// BLAKE3 — install @noble/hashes for portability
import { blake3 } from '@noble/hashes/blake3';
blake3(buf, { dkLen: 32 });   // 32-byte digest

// Decision:
// - Adversary involved (signatures, content auth)?  → SHA-256 / SHA-3 / BLAKE3
// - Just detecting random corruption (CRC-style)?    → MD5/SHA-1 still OK
// - Speed matters on huge files (TB-scale)?          → BLAKE3

Argon2id vs scrypt vs bcrypt — 现代密码哈希

对于密码,直接使用 SHA-256 是错误的。SHA-256 被设计为快速 — 在 GPU 上每秒可计算数十亿次哈希。密码哈希的全部意义就在于足够慢,使被盗数据库无法通过暴力破解获利。自 2000 年代以来,三个算法家族占据主导地位,OWASP 2026 年密码存储备忘单按以下顺序推荐: Argon2id(2015 年 Password Hashing Competition 冠军;2021 年在 RFC 9106 中标准化)。三个参数:内存成本(m,单位 KiB)、时间成本(t,迭代次数)、并行度(p,线程数)。"id"变体混合了数据相关和数据无关的处理路径,可同时抵抗侧信道攻击和时间-内存权衡攻击。OWASP 2026 基线:m=19456(19 MiB)、t=2、p=1,或 m=12288、t=3、p=1,或 m=7168、t=5、p=1 — 选择你的服务器在登录高峰负载下能够承受的那一组。对新系统而言,Argon2id 是毫无争议的首选。主要库:argon2(Node)、argon2-cffi(Python)、org.bouncycastle.crypto.generators.Argon2BytesGenerator(Java)。 scrypt(2009 年,Colin Percival)。两个参数:N(CPU/内存成本因子,必须是 2 的幂)、r(块大小)、p(并行化)。推荐值(2026 年):N = 2^17(约 128 MiB)、r=8、p=1。scrypt 具有内存困难性,但不具备 Argon2 的抵抗特性。如果因兼容性原因不得不继续使用,它仍然可以接受。 bcrypt(1999 年,Niels Provos)。一个参数:成本因子(work factor;每增加 1,成本翻倍)。推荐值(2026 年):根据你的硬件预算取 cost = 12 或 13 — 在 2026 年,低于 10 都太快了。bcrypt 的缺陷在于输入长度有 72 字节上限(更长的密码会被静默截断)、不使用可观的内存,并且对专用硬件没有抵抗能力。它用于遗留系统没有问题,但不是今天新选型时的答案。PBKDF2 更古老也更弱 — 只有在 FIPS 合规强制要求时才可接受。 三者共同的一个关键实现要点:永远不要自己编写 bcrypt 的比较逻辑。verify 函数使用常量时间比较来避免时序攻击。对十六进制编码的摘要使用简单的 == 会泄露前面有多少个字符匹配的信息。请使用 argon2.verify(hash, password) 或 bcrypt.compare(password, hash),绝不要对哈希本身使用相等运算符。 迁移到更强算法时的策略:不要一次性强制所有用户重新哈希。而是在下次登录时,先用旧算法验证通过后,再用新算法重新哈希并存储。这就是"惰性迁移"(lazy migration)模式,也正是 Stack Exchange 在十年间从 MD5 迁移到 bcrypt 再到 Argon2 时采用的做法。
// Argon2id with OWASP 2026 baseline
import argon2 from 'argon2';

const hash = await argon2.hash(password, {
  type: argon2.argon2id,
  memoryCost: 19456,   // 19 MiB
  timeCost: 2,
  parallelism: 1,
});
// Stored format embeds params:
// $argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>

const ok = await argon2.verify(hash, password); // constant-time

// Lazy migration on login
async function login(email, password) {
  const user = await db.users.findOne({ email });
  if (!user) return null;

  if (user.hash.startsWith('$argon2')) {
    if (!await argon2.verify(user.hash, password)) return null;
  } else if (user.hash.startsWith('$2')) { // bcrypt
    if (!await bcrypt.compare(password, user.hash)) return null;
    // upgrade!
    const newHash = await argon2.hash(password, ARGON2_OPTS);
    await db.users.update({ id: user.id }, { hash: newHash });
  }
  return user;
}

密码哈希 vs 非密码哈希 — 选用正确的类别

哈希函数分为设计目标截然不同的两大家族,把一类用在属于另一类的场景中是一个常见的 bug。 密码哈希(SHA-256、SHA-3、BLAKE3、BLAKE2)优先保证面对对抗性输入时的抗碰撞性、抗原像性和抗第二原像性。它们刻意不追求极致速度。只要输入可能被恶意方控制,就应使用它们 — 内容寻址、签名、安全相关上下文中用作标识符的哈希、密码哈希(使用上文的专用函数)、HMAC、密钥派生。在带硬件 SHA 扩展的现代 x86 上,SHA-256 约为 1.5–2 GB/s;BLAKE3 为 5–10 GB/s;SHA-3 比 SHA-2 略慢。 非密码哈希(xxHash、MurmurHash、CityHash、FNV、FarmHash)优先追求原始速度和良好的统计分布。它们对对抗性输入不提供任何保证 — 基于 xxHash 的设计,攻击者可以构造出全部碰撞到同一个桶的输入。xxHash 在现代 CPU 上约为 30+ GB/s(SHA-256 的 10 倍),这正是它存在的全部理由。适用场景:哈希表、Bloom 过滤器、非对抗性数据的去重键、只需担心随机损坏的完整性校验(网络数据包、内存位翻转)、按客户端对日志分桶。 典型的 bug 模式:在对抗性上下文中使用非密码哈希。经典的 2003 年"Crosby & Wallach"论文指出,早期的 Perl、Java 和 PHP 哈希表把这类哈希用于字典键,能够提交表单参数的远程攻击者可以精心构造全部碰撞的输入,把 O(1) 查找变成 O(n),从而对服务器实施 DoS。各语言采用的修复方案(带每进程随机密钥的 SipHash 算法)本身就是一种"带密钥的密码哈希" — 像非密码哈希一样快,但在攻击者不知道密钥时是安全的。 相反的 bug — 用 SHA-256 做哈希表 — 只是慢而已。它能正确工作,但把 CPU 浪费在应用并不需要的密码学保证上。在内层循环和语言运行时中,100 倍的速度差异非常重要。 快速决策树:不可信输入且碰撞影响正确性或安全 → SHA-256 / SHA-3 / BLAKE3。可信或低风险的随机数据且速度重要 → xxHash / MurmurHash。任意用户输入的哈希表键 → SipHash(Python 3.4+、Ruby、Rust 通常由语言替你选择)。密码 → Argon2id。
// CRYPTOGRAPHIC — adversary-resistant
import { createHash } from 'node:crypto';
createHash('sha256').update(buf).digest('hex');
// ~1.5 GB/s on modern x86

// NON-CRYPTOGRAPHIC — fast, NOT for security
import xxhash from 'xxhash-wasm';
const { h64 } = await xxhash();
h64(buf);            // ~30+ GB/s, 64-bit digest
// Use for: hashtables, dedup, sharding non-adversarial data

// HASH TABLE BUG — never use plain non-crypto hash on attacker input
class BadCache {
  constructor() { this.buckets = new Array(1024); }
  set(k, v) {
    const i = murmur(k) % 1024; // attacker can force all to same bucket
    (this.buckets[i] ||= []).push([k, v]);
  }
}

// CORRECT — Map / Object use the language's keyed/hardened hasher
const cache = new Map();
cache.set(userInput, value);  // V8 uses random-keyed hashing

// Non-adversarial integrity (e.g., copy across rack)
const a = h64(file);
const b = h64(receivedFile);
if (a !== b) console.log('corrupted in transit');
最后更新:

关于此工具

哈希生成器使用 MD5、SHA-1、SHA-256、SHA-512 等算法,为任意输入生成固定长度的指纹。哈希是单向的,无法还原。常用于文件完整性校验、内容去重、缓存键生成,以及加盐后的密码校验。

使用方法

  1. 在输入框中输入或粘贴内容 — 文本、JSON 或任意字符串。
  2. 下方会同时生成四种算法的哈希值。
  3. 点击任意一行的 Copy 按钮,将该哈希复制到剪贴板。
  4. 将哈希与公布的校验和进行比对,以验证下载文件。
  5. 任何安全敏感场景请使用 SHA-256 或 SHA-512;MD5 和 SHA-1 仅作遗留用途。

常见用例

  • 验证下载的 ISO 或安装程序与发布者的校验和是否一致。
  • 为 HTTP 响应生成缓存键(ETag)。
  • 为 IPFS 或 git 对象等内容寻址存储生成确定性 ID。
  • 无需逐字节比较即可检查两个大数据块是否相同。
  • 构建 Merkle 树,其中每个节点对其子节点进行哈希。
  • 计算 HMAC 输入(与密钥结合生成带认证的消息标签)。

常见问题

Q. 可以用 MD5 存储密码吗?

A. 不可以。MD5 和 SHA-1 在密码学用途上已被攻破。密码请使用 Argon2id、bcrypt 或 scrypt 等慢速 KDF,而不是原始哈希。

Q. 为什么相同的输入总是产生相同的哈希?

A. 这正是哈希函数的定义性属性。正因如此,哈希才适用于完整性校验和内容寻址。

Q. 两个不同的输入会产生相同的哈希吗?

A. 会 — 这被称为碰撞。SHA-256 和 SHA-512 目前没有已知的实际碰撞;MD5 和 SHA-1 则有。

Q. 哈希是加密吗?

A. 不是。哈希是单向的且没有密钥。加密是双向的(有密钥即可解密)。加密请使用 AES 或 ChaCha20。