元数据卡
- 前置知识:Vol 4 网络(理解 HTTP/TLS 的基本概念),基础数学(模运算、素数)
- 预计时间:60 分钟
- 核心难度:进阶
- 完成标志:能解释对称 vs 非对称加密的适用场景,能说明哈希函数的三个核心性质,能配置 argon2 进行密码哈希
你的进度
你在魔法驿道上奔波了许久,已经能用魔力管道在两座法师塔之间自由传信。
但你在整理信使背包时发现了一个问题:你之前发出的法术信函,全是明文。任何在驿道上架设了魔力监听装置的人,都能读到你的信件内容——包括信件的附魔配方和塔防部署坐标。
你见过 Vol 4 中 TLS 加密法阵的样子,知道它用了某种“魔法”把通信变成了密文。但那是别人造好的防护法阵。现在你要自己理解它的材质——加密咒语就是安全的材料学。 你的任务
理解密码学的四个支柱:对称加密(速度快,适合大量数据)、非对称加密(密钥分发问题的解)、哈希函数(不可逆的指纹)、密钥派生(从密码生成密钥)。这一章的目标不是让你成为密码学家,而是让你能读懂一个安全协议的设计,并知道什么时候用哪种工具。
本章分层
- 必读:对称加密(AES)、非对称加密(RSA)、哈希(SHA-256)、密钥派生(Argon2)
- 选读:ECC 的工作原理、AEAD 模式
- 进阶:侧信道攻击、量子安全的初步概念
破局 · 溯源
问题:你有一封重要信件要送到边境要塞,但沿途可能被截获。
最朴素的想法:把信锁在箱子里。但钥匙怎么送到对方手里?如果钥匙和信走同一条路,强盗拿到钥匙就能开箱。
这就是加密的核心矛盾:你要保护的内容(信)走的是不安全信道,而解密需要的钥匙也必须经过同一信道。
第一把锁:对称加密
对称加密是最直观的方案——加密和解密用同一把钥匙。
明文 + 密钥 -> 加密算法 -> 密文
密文 + 密钥 -> 解密算法 -> 明文AES(Advanced Encryption Standard)是目前最广泛使用的对称加密算法。它是 2001 年 NIST 标准化的一种分组密码(block cipher)。
AES 的核心参数:
| 参数 | 选项 | 说明 |
|---|---|---|
| 密钥长度 | 128 / 192 / 256 bit | 越长越安全;AES-128 目前仍安全 |
| 分组大小 | 128 bit(固定) | 一次加密 16 字节 |
| 模式 | ECB / CBC / GCM / CTR | 影响安全性,ECB 永远不要用 |
用 Python 做一个最简的 AES-GCM 加密(GCM 是推荐模式,因为它自带认证):
# Python 3.11+ 用 cryptography 库
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
# 生成密钥:安全随机,不要自己硬编码
key = AESGCM.generate_key(bit_length=256) # 32 字节
aesgcm = AESGCM(key)
# nonce (number used once): 每次加密必须不同
nonce = os.urandom(12)
plaintext = b"边境报告:发现三处可疑营火"
ciphertext = aesgcm.encrypt(nonce, plaintext, None)
print(f"密文(hex): {ciphertext.hex()}")
# 解密
decrypted = aesgcm.decrypt(nonce, ciphertext, None)
print(f"解密结果: {decrypted.decode()}")保存为 aes_demo.py,运行:
pip install cryptography
python aes_demo.py输出类似:
密文(hex): a1b2c3d4e5f6...
解密结果: 边境报告:发现三处可疑营火GCM 为什么是推荐模式?
AES 本身只加密,不认证。一个攻击者如果截获密文,虽然不能解密,但可以篡改密文中的某些比特。如果解密端不验证完整性,它就会解密出错误的数据。GCM(Galois/Counter Mode)在加密的生成一个认证标签(MAC),任何篡改都会被检测到。
这就是认证加密(AEAD, Authenticated Encryption with Associated Data)的概念。在实际系统中,永远只用 AEAD 模式。ECB 模式不允许用,CBC 模式需要额外加 HMAC,直接用 GCM 或 ChaCha20-Poly1305 省心。
对称加密的软肋
你有了 AES,但有个问题没解决:双方的钥匙怎么约定?
如果你和我素未谋面,我们怎么能安全地商定一个只有我们知道的密钥?这就是密钥分发问题(Key Distribution Problem)。
第二把锁:非对称加密
非对称加密用一对钥匙:公钥公开,私钥保密。公钥加密的内容只有对应的私钥能解开。
明文 + 公钥 -> 加密算法 -> 密文
密文 + 私钥 -> 解密算法 -> 明文RSA 是最经典的非对称加密算法,基于大整数分解的困难性。
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
# 生成密钥对(这是一个较慢的操作)
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048, # 2048 bit 是目前的最低标准
)
public_key = private_key.public_key()
# 用公钥加密(RSA 不能加密大量数据!最大长度 ≈ key_size/8 - padding)
message = b"密钥:AES-GCM: a1b2c3d4"
ciphertext = public_key.encrypt(
message,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None,
),
)
# 用私钥解密
plaintext = private_key.decrypt(
ciphertext,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None,
),
)
print(f"解密结果: {plaintext.decode()}")RSA 的限制很明显:它一次只能加密少量数据(2048 位的 RSA 最多加密 190 字节左右),而且比 AES 慢了上千倍。
混合加密:最佳实践
现实中不会只用一种加密。TLS 握手中,你看到的是这套流程:
1. 客户端用服务端公钥(从证书获得)加密一个"预主密钥"
2. 双方用预主密钥派生出会话密钥(AES 密钥)
3. 后续所有通信用 AES-GCM 加密
优点:既解决了密钥分发(RSA),又获得了高性能(AES)这就是混合加密(Hybrid Encryption)——用非对称加密传递对称密钥,用对称加密保护实际数据。几乎所有现代安全协议(TLS、SSH、Signal)都是这个模式。
现代替代:ECC
ECC(Elliptic Curve Cryptography)是 RSA 的现代替代。同等安全强度下,ECC 的密钥更短、运算更快:
| 安全强度 | RSA 密钥长度 | ECC 密钥长度 |
|---|---|---|
| 80 bit | 1024 | 160-223 |
| 112 bit | 2048 | 224-255 |
| 128 bit | 3072 | 256-383 |
| 256 bit | 15360 | 512+ |
以 128 位安全级别为例:RSA 需要 3072 位密钥,而 ECC 用 256 位就够了。
在实际系统中,你更常见的是 ECDH(Elliptic Curve Diffie-Hellman)密钥交换,而不是直接的 ECC 加密。TLS 1.3 中,ECDHE(基于 ECDH 的临时密钥交换)是主流。
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
# Alice 生成密钥对
alice_private = ec.generate_private_key(ec.SECP256R1())
alice_public = alice_private.public_key()
# Bob 生成密钥对
bob_private = ec.generate_private_key(ec.SECP256R1())
bob_public = bob_private.public_key()
# 双方交换公钥后各自计算共享密钥
alice_shared = alice_private.exchange(ec.ECDH(), bob_public)
bob_shared = bob_private.exchange(ec.ECDH(), alice_public)
assert alice_shared == bob_shared # 两边得到相同的共享密钥
# 用 HKDF 派生为对称密钥
derived_key = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=None,
info=b"handshake data",
).derive(alice_shared)
print(f"共享的对称密钥(hex): {derived_key.hex()}")这个例子展示了 ECDH 的核心:双方能在不安全的信道上协商出一个只有双方知道的共享密钥。如果攻击者截获了 Alice 和 Bob 的公钥,他无法从中计算出共享密钥——这是椭圆曲线离散对数问题的难度保证的。
第三件工具:哈希函数
加密保护的是机密性。但你怎么知道数据没有被篡改?这就是哈希函数的领域。
哈希函数的三条核心性质:
- 抗原像性(单向性):给定
h = H(x),无法反推出x - 抗第二原像性:给定
x,无法找到x' != x使得H(x) = H(x') - 抗碰撞性:无法找到任意
x != y使得H(x) = H(y)
SHA-256 是目前最广泛使用的哈希函数(输出 256 bit / 32 字节):
import hashlib
data = b"边境报告:发现三处可疑营火"
hash_value = hashlib.sha256(data).digest()
print(f"SHA-256: {hash_value.hex()}")
# 任意微小的改变都会导致完全不同的哈希
data2 = b"边境报告:发现三处可疑营火。"
hash2 = hashlib.sha256(data2).digest()
print(f"变了一点: {hash2.hex()}")
print(f"相同? {hash_value == hash2}") # False哈希的典型用途:
| 用途 | 说明 | 示例 |
|---|---|---|
| 数据完整性 | 下载文件后比对哈希,确认未被篡改 | sha256sum |
| 消息认证(HMAC) | 结合密钥,既能认证发送方,又能验证完整性 | HMAC-SHA256 |
| 密码存储 | 存储哈希而不是明文密码 | 见下文 |
| 数字签名 | 先哈希再签名(解决非对称加密只能处理小数据的问题) | RSA-SHA256 |
HMAC(Hash-based Message Authentication Code)解决了一个问题:如果只是用哈希,任何人都能伪造哈希值。加上密钥后,只有拥有密钥的人才能计算有效的哈希:
import hmac
import hashlib
key = b"shared-secret-key"
message = b"今天下午三点在要塞北门会合"
hmac_value = hmac.new(key, message, hashlib.sha256).digest()
print(f"HMAC: {hmac_value.hex()}")
# 验证端用同样的 key 和 message 计算 HMAC,比对密码存储:从明文到 Argon2
存储用户的密码是另一种场景。这里的问题不是"传输安全",而是"存储安全"。
明文存储:一旦数据库泄露,所有密码暴露
单次哈希(SHA-256):容易被彩虹表还原
加盐的 SHA-256:虽然击败了彩虹表,但 GPU 可以每秒算几亿次
慢哈希函数:Argon2、bcrypt、scrypt慢哈希函数(也叫做密钥派生函数,KDF)的设计哲学:让每次计算都足够慢,使得暴力破解变得不现实。
Argon2 是 2015 年密码哈希竞赛的胜者,有三个变体:
- Argon2d:抵抗 GPU 攻击(数据依赖的内存访问)
- Argon2i:抵抗侧信道攻击
- Argon2id:混合模式(推荐)
# 用 argon2-cffi 库(pip install argon2-cffi)
from argon2 import PasswordHasher
ph = PasswordHasher(
time_cost=3, # 迭代次数
memory_cost=65536, # 64 MB 内存
parallelism=4, # 并行线程
hash_len=32, # 输出哈希长度
salt_len=16, # 盐长度
)
password = "correct-horse-battery-staple"
hash_str = ph.hash(password)
print(f"Argon2 哈希:\n{hash_str}")
# 验证
try:
ph.verify(hash_str, "correct-horse-battery-staple")
print("验证通过")
except:
print("验证失败")hash_str 的输出格式包含了所有必要参数,方便存储和迁移:
$argon2id$v=19$m=65536,t=3,p=4$<salt_hex>$<hash_hex>即使数据库泄露,攻击者要破解一个 Argon2 密码也需要几十毫秒的计算时间。一万个密码就需要几百秒,几十万个就需要几个小时。而如果用 SHA-256,几秒钟就能算完。
常见陷阱
- 使用 ECB 模式。ECB 模式下,相同的明文块产生相同的密文块。在一个图片加密的例子中,ECB 加密后的图片仍然可见轮廓。不要用 ECB。
- 自己发明加密算法。即使是经验丰富的密码学家也经常失败。用标准库和经过审计的实现。
- 硬编码密钥。密钥应该从环境变量、密钥管理系统或 HSM 中获取,而不是写在代码里。
- 使用过时的算法。MD5 和 SHA-1 已经被攻破碰撞性,不要用于安全场景。RC4 和 DES 已被彻底破解。
- 忽略随机数质量。
random模块不是安全的随机数生成器。用os.urandom或secrets。 - RSA 对短消息直接加密。2048 位 RSA 最多加密约 245 字节。要用混合加密方案。
通关挑战
- 热身:用 AES-GCM 加密一条消息,然后故意修改密文的最后一个字节,观察解密失败。
- 挑战:用 ECDH 实现一个"双方在纸上交换公钥就能共享密钥"的演示。把共享密钥派生为 AES 密钥,再用它加密和解密一段消息。
- 排障:你发现一个在线服务存储密码用的是
SHA256(password)。设计一次攻击方案,然后向团队提出改用 Argon2 的技术方案。 - 观察:运行
openssl speed aes-128-gcm和openssl speed rsa2048,对比对称和非对称加密的性能差异(量级上的差异)。
旅人笔记
- 对称加密(AES-GCM)解决传输安全,但需要解决密钥分发问题
- 非对称加密(RSA/ECC)解决密钥分发,但速度慢、容量小
- 实际系统用混合加密:非对称握手、对称传输
- 哈希函数提供完整性验证,但不能替代加密
- 存储密码用慢哈希(Argon2),不要用快速哈希或明文
下一站预告
现在你有一把加密这把锁了。但你怎么知道握在手里的公钥真的是对方的?下一章,我们走进 PKI——数字世界的信任网络。