跳到主要内容
加密解密2026-06-185 分钟阅读

哈希 vs 加密:有什么区别?什么时候该用哪个

"哈希和加密不是一回事吗?" 这是新手最常问的问题,也是导致密码泄露事件的常见根因。把加密用在哈希的场景(或反过来),后果可能很严重:用可逆加密存密码,攻击者拿到密钥就能解出所有密码;用不可逆哈希存需要恢复的数据,原始信息就永久丢失了。

这篇指南讲清楚两件事:哈希和加密在数学上的根本区别,以及在实际项目里如何正确选择。想亲眼看一下哈希的单向性?用我们的 SHA-256 哈希生成器 算一个摘要试试。

一句话区分

哈希不可逆,加密可逆。

  • 哈希:把任意输入变成固定长度的"指纹"。你无法从指纹还原出原始输入。
  • 加密:用密钥把明文变成密文。有密钥就能还原明文。

这个差别决定了所有其他差异。

哈希:单向函数

哈希函数接收任意大小的输入,输出固定长度的摘要。关键特性:

  • 确定性:相同输入永远产生相同输出
  • 单向:无法从输出反推输入(计算上不可行)
  • 抗碰撞:找不到两个不同输入产生相同输出(理论可能,实际不可行)

常见哈希算法:

# SHA-256(32 字节,64 个十六进制字符)
echo -n "hello" | shasum -a 256
# 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

# MD5(16 字节,32 个十六进制字符,已不安全)
echo -n "hello" | md5
# 5d41402abc4b2a76b9719d911017c592

哈希的典型用途:

  • 文件完整性校验:下载大文件后对比哈希,确认没有损坏或被篡改(可以用在线哈希校验工具直接比对 MD5/SHA256)
  • 内容寻址存储:Git 用对象哈希作为唯一标识
  • 缓存键和去重:相同内容产生相同键,快速查找
  • 密码存储:这里有个大坑,下面单独说

想亲手算一个哈希对比效果,SHA256 在线计算器支持 SHA-1/256/512 全家族,输入即出结果。

加密:双向函数

加密需要密钥。加密用密钥把明文变成密文,解密用(相同或不同)密钥还原。

两种主要类型:

对称加密:加密和解密用同一个密钥。速度快,适合大量数据。浏览器里就能跑的 AES 加解密工具用的就是这类算法(Web Crypto API 实现,密钥不出本机)。

# AES-GCM(对称加密的工业标准)
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os

key = AESGCM.generate_key(bit_length=256)
nonce = os.urandom(12)
aesgcm = AESGCM(key)

ciphertext = aesgcm.encrypt(nonce, b"secret message", None)
plaintext = aesgcm.decrypt(nonce, ciphertext, None)
# plaintext == b"secret message"

非对称加密:一对密钥,公钥加密,私钥解密。慢,但解决了密钥分发问题。

# RSA:用公钥加密,私钥解密
openssl rsautl -encrypt -pubin -inkey public.pem -in message.txt -out encrypted.bin
openssl rsautl -decrypt -inkey private.pem -in encrypted.bin -out message.txt

加密的典型用途:

  • 传输层安全:TLS 用混合加密保护 HTTPS 流量
  • 静态数据保护:磁盘加密、数据库字段加密
  • 数字信封:用对称密钥加密数据,再用非对称密钥加密对称密钥
  • 任何需要恢复原始数据的场景

密码存储:最容易踩的坑

密码存储是哈希和加密混淆最频繁的场景。规则很明确:

永远不要加密密码。永远不要用裸哈希存密码。

加密意味着有密钥能还原。一旦密钥泄露(往往就存在同一台服务器上),所有密码立刻明文暴露。

裸 SHA-256 或 MD5 同样不可接受。它们太快,现代 GPU 每秒能尝试几十亿次哈希,简单的字典攻击几小时就能破解大量弱密码。

正确做法是使用专门的密码哈希函数:

import bcrypt

# 哈希密码(bcrypt 自动加盐)
hashed = bcrypt.hashpw(b"mypassword", bcrypt.gensalt(rounds=12))
# b'$2b$12$...'

# 验证密码
bcrypt.checkpw(b"mypassword", hashed)  # True
bcrypt.checkpw(b"wrongpassword", hashed)  # False

密码哈希函数(bcrypt、scrypt、argon2id)的关键特性是故意很慢。它们通过"工作因子"参数让单次哈希耗时可控(通常几百毫秒),让暴力破解在计算上不可行。Argon2id 是 2015 年密码哈希竞赛的获胜者,是目前推荐的黄金标准。

何时用哈希 vs 加密

简单的判断:你需要能恢复原始数据吗?

  • 需要 → 加密
  • 不需要 → 哈希

展开成决策表:

场景用哈希用加密
用户密码✓(用 bcrypt/argon2)✗
API 请求签名✓(HMAC)✗
文件完整性校验✓✗
缓存键 / 去重✓✗
数据库字段(需查询原值)✗✓
通信内容(TLS、消息)✗✓
备份归档✗✓
数字签名✓(哈希后签名)✓(非对称签名)

注意最后一行:数字签名同时用到两者。先对原文做哈希压缩,再用非对称密钥对哈希签名,避免对整个文档做昂贵的非对称运算。

性能对比

哈希通常比加密快(虽然现代 CPU 都有硬件加速,差距已经不大):

操作现代硬件吞吐量
SHA-256(带 SHA-NI)~1.2 GB/s
AES-256-GCM(带 AES-NI)~5 GB/s
bcrypt(rounds=12)~100 次/秒

注意 bcrypt 的吞吐量是"次/秒",而不是"GB/秒"。这种慢是有意为之的,对密码哈希反而是优点。

HMAC 与带密钥哈希(仍然不是加密)

HMAC-SHA256 一类用共享密钥证明完整性与真实性,并不隐藏报文。把「我们对 body 做了 HMAC」当成「body 已保密」,是常见的 API 设计误区。机密性靠 TLS(或载荷加密);防篡改靠 HMAC 或数字签名。HMAC 生成器适合本地复现请求签名——它同样不会加密任何内容。

在本地完成敏感操作

无论你要哈希 API 密钥、加密配置文件,还是生成密码哈希,都应该尽量避免把敏感数据发送到陌生服务器。

SHA-256 哈希生成器、MD5 哈希生成器、bcrypt 哈希、AES 加解密 和 RSA 加解密 都完全在浏览器中运行,使用 Web Crypto API 处理输入。你的明文、密钥、密码或哈希结果从不离开你的机器。这在处理 API 凭证、客户数据、专有代码等敏感内容时尤其重要,没有日志、没有留存、没有第三方窥探。

总结:需要恢复原文用加密,不需要用哈希,存密码用 bcrypt 或 argon2,永远不要混淆这三件事。

实现注记

文章反复说哈希用于「完整性校验」,实现上有个容易漏的边角:大文件哈希要跑数十秒,期间用户换文件或换算法,旧结果晚到会张冠李戴——文件名 A 配上哈希 B。校验工具内部需要乱序守卫拦掉过期结果,否则工具自己就在制造「校验通过」的假象。