哈希 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。校验工具内部需要乱序守卫拦掉过期结果,否则工具自己就在制造「校验通过」的假象。
相关工具
相关文章
后量子迁移自查清单:从算法盘点到 hybrid TLS,2026 年该做完的 12 件事
后量子密码(PQC)迁移的实操自查框架:三张监管时刻表为什么让 2026 成为起点、算法盘点(CBOM)怎么做、按 HNDL 暴露度排优先级、X25519+ML-KEM 混合 TLS 怎么开,以及 crypto-agility 的工程落地。附 12 项可勾选 checklist。
Passkeys 完全指南:为什么服务器再也存不了你的密码副本
Passkey(WebAuthn/FIDO2)加深:注册与断言仪式、平台认证器与漫游密钥、同步通行密钥取舍、与密码/TOTP/JWT 的关系,以及 origin 绑定如何结构性抗钓鱼。服务器只存公钥;站内无独立 Passkey 工具页的说明。
ML-KEM 入门:Kyber 是怎么进到你的 TLS 握手里的
后量子密钥封装 ML-KEM(FIPS 203,前身 Kyber)的工程向解读:Kyber 到标准名的变迁、封装/解封装语义与 IND-CCA、三档参数尺寸、X25519+ML-KEM 与 TLS 1.3 HKDF 密钥调度的混合方式、ClientHello 膨胀中间盒坑、OpenSSL 3.5 / nginx 开法,以及浏览器侧实现边界。