把「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 的规范人手一份),保密性完全依赖密钥。这带来三个工程上常被忽略的推论:
- 密钥管理比算法选择更容易出错:密钥硬编码在源码里、写进配置文件提交到仓库,比「用了 ECB 模式」更常见也更致命。
- 算法公开不是弱点:Kerckhoffs 原则——系统应当仅在密钥保密的前提下保证安全。
- 模式选择同样重要:AES-ECB 对重复明文块产生重复密文(经典的「ECB 企鹅」),同样用 AES-256-GCM 则既保密又可校验完整性。模式对比见 AES 加密模式选型。
怎么选:三类场景速查
| 你要做什么 | 正确工具 | 不要用 |
|---|---|---|
| 用户登录密码存库 | bcrypt / argon2(加盐慢哈希) | |
| 校验下载文件是否被篡改 | SHA-256 比对发布方公布的哈希 | |
| 让二进制数据过文本通道 | Base64 / Base64URL | |
| 数据库里的敏感字段 | AES-GCM 等认证加密 | |
| 接口防篡改 | HMAC-SHA256(带密钥的哈希) |
第三行是最容易反过来的:哈希不可逆,意味着用哈希「存」的数据也取不回来——加密手机号以便后续解密展示,用哈希就做不到。
可复现的实测结果
同一段输入 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 转换器,全部本地运行,验证这些结论不需要上传任何数据。