在线工具集

哈希 vs 加密:常见误区与正确用法

深入理解哈希与加密的本质区别(单向 vs 双向)、为什么密码必须哈希不能加密、bcrypt/argon2 加盐与慢哈希、HMAC 防篡改、SHA-256 vs SHA-3、彩虹表与 Salt、应用错配(MD5 存密码/AES 做指纹)。

📅 发布于 2026-04-29 · ⏱ 约 15 分钟阅读

"我们用 AES 加密密码存在数据库" "用 MD5 做文件完整性检查" "这个 API 用 SHA-256 签名,很安全"。这些说法看似懂密码学,其实满是错误。哈希和加密是两个完全不同的概念,混淆它们会导致严重的安全漏洞。本文将深入探讨这两个概念的本质区别、常见误区、为什么密码必须哈希而非加密、现代密码存储最佳实践(bcrypt/argon2)、如何用盐值和 HMAC 防御攻击,以及过时算法(MD5、SHA-1)为什么已不可用。

1. 哈希与加密的本质区别

这两个概念经常被混淆,但它们在安全属性上是完全对立的。让我们用一个简单的类比:

加密:可逆的密钥保护

加密像是一把安全门锁。你用钥匙(密钥)锁上,收件人用相同或配对的钥匙解锁。

  • 明文 + 密钥 → 加密 → 密文
  • 密文 + 密钥 → 解密 → 原始明文
  • 例子:AES、RSA、ChaCha20
  • 应用:通信保密、文件加密、数据库字段加密

关键特性: 可逆、确定性(相同输入+密钥 = 相同输出)、需要密钥管理。

哈希:不可逆的指纹

哈希像是一个指纹识别系统。输入信息被转换成一个固定长度的"指纹",无法从指纹还原出原始信息。

  • 输入数据 → 哈希 → 哈希值(指纹)
  • 无法从哈希值逆向得到原始数据
  • 例子:SHA-256、SHA-3、MD5(已过时)
  • 应用:密码存储、文件完整性检查、区块链、数字签名

关键特性: 不可逆、确定性、快速、碰撞概率极低。

对比表

特性 加密 哈希
可逆性 可逆(用密钥) 不可逆
需要密钥
速度 中等(毫秒) 快(纳秒)或慢(毫秒/密码)
输出长度 可变(等于输入) 固定(256bit、512bit 等)
确定性 可选(可用 IV/盐) 通常确定(除密钥相关)

2. 单向函数:为什么哈希无法逆向

单向函数的数学性质

哈希函数是一个单向函数(One-way Function),具有以下性质:

  • 正向容易 — 给定输入 X,计算 H(X) 很快(纳秒级)
  • 逆向困难 — 给定哈希值 H,找到任何输入 X 使得 H(X) 等于给定值是不可行的
  • 碰撞稀有 — 找到两个不同输入产生相同哈希(碰撞)极其困难

为什么不可逆?因为哈希涉及信息损失。从 512 位输入压缩到 256 位输出,本质上丢弃了一半的信息。即使尝试所有可能的输入,也无法确定哪个产生了特定的哈希。

实际例子:SHA-256

SHA-256 哈希函数如下:

输入:"password"

SHA-256 哈希:5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8

 

输入:"password123"

SHA-256 哈希:ef797c8d8f992f5b5614653fc3740ce5cb20963d2c0dda6df10d4afab0aeff59c

即使你看到哈希值 `5e884898...`,也不可能通过逆向计算还原出 "password"。唯一的方法是尝试所有可能的输入,看是否能匹配这个哈希。对于强密码,这需要数十亿年。

为什么加密不适合密码存储

如果用加密存储密码:

密码存储(错误方法):

密钥 K = "secretkey123"

用户密码 P = "user_password"

加密后存储:密文 C = AES_Encrypt(P, K)

问题:如果数据库泄露,黑客获得 K 和 C,立即可解密得到 P。

相比哈希存储:

密码存储(正确方法):

用户密码 P = "user_password"

哈希后存储:H = bcrypt(P) = "$2b$12$..."

问题:黑客即使获得 H,也无法逆向得到 P。只能暴力尝试。

3. 密码存储:为什么必须哈希不能加密

从零开始理解密码验证流程

用户注册时,系统需要存储密码以供后续登录验证。但如何安全地存储是核心问题。

方案 1:明文存储(灾难)

最不安全。数据库一泄露,所有密码都暴露。现实中曾有大量网站这样做,结果造成巨大安全事故。

方案 2:用加密存储(错误)

注册:密文 = AES_Encrypt("my_password", serverKey)

登录:接收用户输入,加密后与数据库密文比较。

问题:serverKey 必须存在服务器上。如果服务器被攻破或有内部人员作恶,获得密钥即可解密所有密码。

方案 3:用快速哈希(不够好)

注册:存储 hash = SHA-256("my_password")

登录:哈希用户输入,与存储的哈希比较。

改进:只有知道原密码才能验证成功(因为哈希无法逆向)。

问题:SHA-256 太快(纳秒级)。黑客可用暴力破解尝试数十亿个密码,在短时间内找到匹配。常见密码(如 "123456"、"password")有预计算的哈希值(彩虹表),查表即可。

方案 4:用慢哈希 + 盐值(推荐)

注册:

salt = random(16 bytes)

hash = bcrypt("my_password", cost=12, salt=salt)

store: (hash, salt)

登录:

input_hash = bcrypt(user_input, cost=12, salt=stored_salt)

if input_hash == stored_hash: login success

优势:

  • bcrypt 很慢(cost=12 约 100ms),降低暴力破解速度
  • 随机盐值确保相同密码产生不同哈希,彩虹表失效
  • 即使密码被猜中,验证速度也只是 100ms,用户几乎不感觉

4. 盐值与彩虹表攻击

彩虹表:预计算哈希表

彩虹表是攻击者预先计算的哈希表。它包含常见密码及其哈希值:

密码 | SHA-256 哈希

--- | ---

123456 | 8d969eef6ecad3c29a3a873fba5d659b32fdb7f7

password | 5e884898da28047151d0e56f8dc6292773...

qwerty | d0303db1c62fe41f69fa3ee06b4e73c0e3a...

… (数十亿条记录)

黑客从泄露的数据库中取出哈希,只需查表就能迅速找到对应密码。预计算表通常几个 GB,包含常见的数十亿个密码,查询极快。

盐值的防御原理

盐值(Salt)是在哈希前混入的随机数据。关键是每个用户使用不同的盐:

用户 A:

盐 = "a1b2c3d4..."

密码 = "password"

hash = SHA-256("password" + "a1b2c3d4...") = "xyz..."

用户 B:

盐 = "e5f6g7h8..."

密码 = "password"(相同)

hash = SHA-256("password" + "e5f6g7h8...") = "abc..."(完全不同)

即使两个用户密码相同,盐值不同,最终哈希也完全不同。彩虹表失效,因为表中没有考虑这么多种盐的组合。

盐值的最佳实践

  • 长度 — 至少 16 字节(128 比特)
  • 随机性 — 用密码学安全的随机生成器
  • 唯一性 — 每个密码都应使用不同的盐
  • 存储 — 盐可明文存储(与哈希一起),它的作用是增加彩虹表的规模

好消息:bcrypt 和 argon2 都自动处理盐值生成和存储。开发者只需调用函数,不用手动管理。

5. bcrypt 和 argon2:慢哈希的力量

为什么慢哈希更安全?

快速哈希(SHA-256)设计用来哈希大量数据,速度是目标。但对密码存储,快就成了弱点——黑客可快速尝试数十亿个猜测。

慢哈希故意设计得慢:每次计算需要 100ms~1s。这对真实用户几乎无感(只登录一次),但对黑客破坏性极强:尝试 1 亿个密码需要 3000 年。

bcrypt:业界标准(25 年)

bcrypt 由 Niels Provos 和 David Mazières 在 1999 年设计,基于 Blowfish 加密算法。至今仍是最广泛使用的密码哈希方案。

Hash 格式:$2b$12$R9h/cIPz0gi.URNNGHQ1ve0dgaAZeKqtxWzwvyWI5SE.tmfGvSxy.

 

字段含义:

$2b$ — bcrypt 算法标识

$12$ — 工作因子(cost)= 12(2^12 轮)

R9h/cIPz... — 盐值(22 字符)+ 哈希值

关键参数:Cost(工作因子)

  • Cost = 10 → ~10ms
  • Cost = 12 → ~100ms
  • Cost = 14 → ~1s
  • 每增加 1,时间翻倍

推荐: 新应用使用 Cost = 12。如果需要额外安全,随时可升至 14(已有哈希不需重算)。

argon2:新一代标准(2015)

argon2 在 2015 年赢得 Password Hashing Competition。相比 bcrypt,它更灵活、参数更多、抗 GPU 攻击更强。

参数:

  • Time cost — 迭代次数(多次通过)
  • Memory cost — 内存使用量(MB)
  • Parallelism — 并行度

优势:

  • 内存硬度(Memory-hard)— 不仅吃 CPU,也吃内存,更难被 GPU 加速
  • 三种变体 — Argon2i(抗 GPU)、Argon2d(快速)、Argon2id(混合)
  • 参数灵活 — 可独立调整时间、内存、并行度

推荐配置(2026):

Time cost = 2

Memory cost = 65536 KB (64 MB)

Parallelism = 4

预期耗时:~500ms

bcrypt vs argon2:选择建议

  • bcrypt — 成熟、库支持好、足够安全。适合大多数应用。
  • argon2 — 更新、参数丰富、抗硬件加速更强。适合超高安全需求(银行、军事)。

6. HMAC:消息认证与完整性校验

问题:纯哈希无法验证来源

假设服务器发送一条消息及其 SHA-256 哈希:

消息:"转账 1000 元给 Alice"

哈希:SHA-256(消息) = "abc123..."

问题:中间人可以同时修改消息和哈希。

攻击者修改为:"转账 1000 元给 Bob"

重新计算哈希:SHA-256(新消息) = "def456..."

接收者收到新的消息和新的哈希,验证通过(虽然内容改了)

HMAC 的解决方案

HMAC(Hash-based Message Authentication Code)使用密钥和哈希函数组合,确保来源真实且内容未被篡改。

secret_key = "server_secret_key_12345"

message = "转账 1000 元给 Alice"

 

HMAC = HMAC-SHA-256(secret_key, message)

= "xyz789..."

 

发送:(message, HMAC)

关键:攻击者没有 secret_key,无法计算正确的 HMAC。即使修改消息,新的 HMAC 也是错的。

HMAC 的应用场景

  • API 签名 — AWS、Stripe 等 API 都用 HMAC 签名请求
  • Cookie 防篡改 — web 框架用 HMAC 保护 session 数据
  • 日志完整性 — 确保日志在传输和存储中未被修改
  • 消息队列 — RabbitMQ、Kafka 等用 HMAC 验证消息真实性

HMAC vs 数字签名

两者都能验证来源和完整性,但不同:

  • HMAC — 对称(双方共享密钥),适合内部系统通信
  • 数字签名 — 非对称(私钥签名,公钥验证),适合公开验证

7. SHA-256 vs SHA-3:算法演进

SHA-256(SHA-2 家族)

SHA(Secure Hash Algorithm)由美国 NSA 设计,发表于 1993 年。SHA-256 是 SHA-2 系列中最常用的版本。

  • 输出长度 — 256 比特(32 字节)
  • 安全强度 — 128 比特(时间成本 2^128)
  • 速度 — 快(现代 CPU 可达 GB/s)
  • 使用场景 — 文件完整性检查、区块链、TLS、一般哈希

至今仍被广泛使用,安全性已被 30 年的实践验证。

SHA-3(最新标准)

2015 年,NIST 采纳 SHA-3 为新的标准哈希算法。基于 Keccak 算法,采用完全不同的数学结构(海绵函数)。

  • 输出长度 — 256 比特(32 字节)或更长
  • 安全强度 — 128+ 比特
  • 速度 — 中等(比 SHA-256 略慢)
  • 特点 — 对位翻转、边界情况更鲁棒

SHA-2 vs SHA-3 对比

特性 SHA-256 SHA-3-256
发布年份 2001 2015
结构 Merkle-Damgård 海绵函数
已知弱点 无重大弱点 无已知弱点
性能 ~500 MB/s ~100 MB/s
推荐用途 现有系统继续 新项目优先

选择建议

  • 现有项目 — SHA-256 完全足够,无需迁移
  • 新项目 — 优先使用 SHA-3-256(更新、更稳健)
  • 密码存储 — 使用 bcrypt 或 argon2,不直接用 SHA-256

8. 为什么 MD5 和 SHA-1 已死

MD5(1992)— 永远不要用

MD5 输出 128 比特,一度很受欢迎。但在 2004 年,王小云证明了 MD5 的碰撞(两个不同输入产生相同哈希)。

MD5 碰撞示例:

Input 1: d131dd02c5e6eec4693d61a8c169...(64 字节)

Input 2: d131dd02c5e6eec4693d61a8c169...(64 字节,中间不同)

MD5 Output: 79054025255fb1a26e4b0d753b789d82(两者相同)

风险:

  • 不能用于文件完整性检查(攻击者可修改文件和哈希)
  • 不能用于数字签名(可伪造签名)
  • 不能用于密码存储(虽然密码本身一般不会碰撞,但多个 MD5 数据库可互通)

SHA-1(1995)— 基本退役

SHA-1 输出 160 比特,比 MD5 更强,长期被用于 HTTPS 证书和 Git。但在 2017 年,Google 和 CWI Amsterdam 联合发表了 SHA-1 的实际碰撞(SHAttered 项目)。

  • 难度远低于理论,实际可在数周内计算
  • 2019 年,主流浏览器完全停止接受 SHA-1 证书
  • Git 社区正逐步迁移到 SHA-256

现在应该做什么

旧算法 替代品 迁移优先级
MD5 SHA-256 或 SHA-3 立即
SHA-1(非签名) SHA-256 或 SHA-3
SHA-1(证书) SHA-256 已完成(浏览器强制)

常见问题

为什么密码必须哈希而不能加密?

如果用加密存密码,数据库一旦泄露,黑客可获得密钥或解密方式,立即解出所有密码。而哈希是单向的,即使获得哈希值也无法反向得到原密码。此外,即使两个用户密码相同,使用不同的盐值也会产生不同的哈希,防止彩虹表攻击。

什么是彩虹表?如何用盐值防御?

彩虹表是常见密码的预计算哈希表。黑客可直接查表匹配哈希。盐值是加密学中的随机数,与密码混合后再哈希,使每个密码的哈希都独一无二。即使两用户密码相同,盐值不同,哈希也不同,彩虹表失效。

bcrypt 和 argon2 为什么比 SHA-256 更好?

SHA-256 是快速哈希(纳秒级),容易被暴力破解。bcrypt 和 argon2 是慢哈希(毫秒级),内置盐值和工作因子,每次计算可调慢。这使攻击者需要更长时间破解,而真实用户只需等待一次。bcrypt 被广泛使用,argon2 更新且参数更灵活。

HMAC 是什么?为什么需要它?

HMAC 是消息认证码,用密钥和哈希函数组合,确保消息来源真实且未被篡改。与纯哈希不同,HMAC 需要密钥,攻击者无法自行计算正确的 HMAC。常用于 API 签名、Cookie 防篡改。

SHA-256 和 SHA-3 有什么区别?

SHA-256 是 SHA-2 系列,广泛使用,安全性已被验证 20+ 年。SHA-3 是更新的标准(2015),基于不同的数学结构,对边界情况更稳健,性能更好。两者都很安全,SHA-3 只有在需要额外保险时才必要,但新项目可优先 SHA-3。

为什么 MD5 和 SHA-1 不能再用?

MD5 和 SHA-1 都有已知的碰撞攻击。在 2004 年,研究者找到 MD5 碰撞方法;2017 年,Google 展示了 SHA-1 的实际碰撞。这意味着黑客可构造两个不同输入产生相同哈希,破坏完整性校验。不应用于密码、数字签名或文件校验。