← 返回博客首页

加密、哈希与编码的区别:一张表讲清三者边界

把「Base64 编码」当成「加密」,把「MD5 哈希」当成「可逆转换」——这两类混淆不是面试题里的小错误,而是真实的事故来源:密码被 Base64 编码后存库(等于明文)、接口把「签名」和「编码」混为一谈导致验签失效、下载文件只对了一次 MD5 就信了安装包。三者的边界其实只有三个维度:可逆性、密钥、输出。


一张表讲清三者边界

维度 编码(Encoding) 哈希(Hashing) 加密(Encryption)
目的 让数据适合某个通道/格式 验证完整性 / 生成指纹 保护机密性
可逆性 可逆(编码表公开) 不可逆(信息被丢弃) 可逆(需要密钥)
密钥 无 无(密码存储需另加盐) 必需,且保密
输出形态 字符集内的文本 固定长度摘要 与明文等长的密文
同输入输出 确定 确定 确定性模式确定;带随机 IV 则每次不同
典型代表 Base64、URL 编码、Hex MD5、SHA-256、bcrypt AES-GCM、ChaCha20、RSA
谁攻击它 不构成攻击(人人可解) 碰撞攻击 / 暴力枚举 破解密钥 / 利用模式缺陷
典型用途 传输兼容、URL 参数 文件校验、密码存储、指纹 数据保密、通信加密

一句话版本:编码是换表示,哈希是留指纹,加密是上锁。锁需要钥匙,指纹不能还原,换表示人人都会。


编码不是加密:30 秒可证

「Base64 出来的是乱码,所以它加密了」是最常见的误区。乱码感的来源只是字符表不同——编码表是公开标准,解码一步完成:

echo -n "admin:123456" | base64     # → YWRtaW46MTIzNDU2
echo "YWRtaW46MTIzNDU2" | base64 -d # → admin:123456(任何人,一步)

没有密钥参与,谈不上保密。Base64 的正确定位是传输兼容:让二进制数据穿过只接受文本的通道(邮件正文、JSON 字符串)。详见 Base64 编码详解。

同理,URL 编码(%E4%BD%A0)、Unicode 转义(\u4f60)也都是编码:解决「字符在某个格式里能不能合法出现」的问题,与保密无关。


哈希为什么不可逆:信息被丢掉了

另一个常见困惑:「MD5 不可逆,那网上那些 MD5 解密网站是怎么回事?」

SHA-256 把任意长度的输入压缩成固定的 32 字节摘要。这个过程中大量信息被丢弃:256 bit 的输出空间里只有 2²⁵⁶ 种可能,而可能的输入是无穷的——无数个不同输入可以对应同一个摘要。摘要里根本没有足够的信息还原原文。

「解密网站」的原理因此不是反推,而是查表:预先算好海量常见字符串的哈希,拿你的哈希去比对。这也正是「裸哈希存密码」不安全的全部原因——破解者不需要理解 SHA-256,只需要一张够大的表。

对密码存储的正确姿势:加盐 + 慢哈希(bcrypt / argon2 / scrypt)。盐让同一密码在不同用户处产生不同哈希(彩虹表失效),慢哈希让每次枚举的代价从纳秒级抬到几十毫秒。对比与选型见 密码哈希指南。


加密的核心是密钥,不是算法

加密算法几乎都是公开的(AES、ChaCha20 的规范人手一份),保密性完全依赖密钥。这带来三个工程上常被忽略的推论:

  1. 密钥管理比算法选择更容易出错:密钥硬编码在源码里、写进配置文件提交到仓库,比「用了 ECB 模式」更常见也更致命。
  2. 算法公开不是弱点:Kerckhoffs 原则——系统应当仅在密钥保密的前提下保证安全。
  3. 模式选择同样重要:AES-ECB 对重复明文块产生重复密文(经典的「ECB 企鹅」),同样用 AES-256-GCM 则既保密又可校验完整性。模式对比见 AES 加密模式选型。

怎么选:三类场景速查

你要做什么 正确工具 不要用
用户登录密码存库 bcrypt / argon2(加盐慢哈希) MD5、Base64、AES(可被解密=可被取回)
校验下载文件是否被篡改 SHA-256 比对发布方公布的哈希 MD5(已可构造碰撞)
让二进制数据过文本通道 Base64 / Base64URL 加密(不需要密钥的事别引入密钥管理)
数据库里的敏感字段 AES-GCM 等认证加密 Base64、哈希(不可逆就取不回来了)
接口防篡改 HMAC-SHA256(带密钥的哈希) 裸 MD5 拼接

第三行是最容易反过来的:哈希不可逆,意味着用哈希「存」的数据也取不回来——加密手机号以便后续解密展示,用哈希就做不到。


可复现的实测结果

同一段输入 hello world 分别走三种处理,终端 30 秒验证全部结论:

import base64, hashlib
msg = b'hello world'

print(base64.b64encode(msg).decode())      # aGVsbG8gd29ybGQ=   ← 编码:可逆、无密钥
print(base64.b64decode(base64.b64encode(msg)))  # b'hello world' ← 一步还原
print(hashlib.sha256(msg).hexdigest())     # b94d27b9934d3e08…  ← 哈希:不可逆
print(len(hashlib.sha256(msg).digest()))   # 32(256 bit)

再用 OpenSSL 真实跑一次 AES-CBC 加密(密钥与 IV 显式给定,保证可复现):

echo -n "hello world" | openssl enc -aes-128-cbc \
  -K 000102030405060708090a0b0c0d0e0f \
  -iv f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff | xxd -p
# → 一段密文;把 -K 换成任何其它 16 字节密钥,输出完全不同(密钥决定一切)

三个输出形态的对比就是三者的边界:编码变了表示、哈希丢了信息、加密上了锁。


小结

  • 编码解决「格式兼容」,人人可逆,与保密无关;
  • 哈希解决「验证一致」,不可逆,安全性取决于是否加盐与是否够慢;
  • 加密解决「保密」,可逆但需要密钥,安全性取决于密钥管理与模式选择。

三者的工具本站都有:AES 加密/解密、文本哈希、Base64 转换器,全部本地运行,验证这些结论不需要上传任何数据。


广告

常见问题

Base64 能用来「加密」密码吗?

**不能。** Base64 是编码:没有密钥、字符表公开,任何人一步就能还原。把 `admin123` 编码成 `YWRtaW4xMjM=` 存进数据库,攻击者解码与读明文没有任何区别。「看起来乱码」不等于「保密」。密码应该用 bcrypt/argon2 这类慢哈希存储。

哈希既然不可逆,为什么还能被破解?

哈希不可逆是指**无法从哈希值反推原文**,但攻击者不需要反推——他们可以**暴力枚举**常见密码、逐个计算哈希并与库里的值比对。所以裸 MD5/SHA-256 存密码不安全:现代 GPU 每秒可计算数十亿次。防御靠**加盐 + 慢哈希**(bcrypt/argon2),让每次猜测的代价足够高。

同一个文件每次算出的哈希都一样,这不是不安全吗?

恰恰相反——**确定性**正是哈希能用于完整性校验的原因:同输入必同输出,任何一字节改动都会让哈希面目全非(雪崩效应)。「安全性」取决于用途:校验下载文件用 SHA-256 足够;存密码则必须加盐并使用慢哈希,否则确定性反而方便了彩虹表攻击。

JWT 里那串 Base64 是被加密的内容吗?

不是。JWT 的三段分别是 **Base64URL 编码的 Header、Payload 和签名**——前两段任何人都能解开阅读(本站就有 [JWT 解析器](/jwt-parser.html))。真正防篡改的是第三段**签名**:改动 Payload 而不重算签名,校验就会失败。所以 JWT 的 Payload 里不要放敏感明文。

← 返回博客首页