第 4 章 密码学基础与应用
4.1 学习目标
- 理解对称 / 非对称加密、哈希、MAC、数字签名的作用边界与组合原则。
- 掌握主流算法(AES、ChaCha20、RSA、ECC、SHA-2/3、HMAC、Ed25519、Argon2)的使用场景与工程参数选择。
- 理解背后的数学:模算术、离散对数、椭圆曲线、格密码。
- 能复现并防御常见误用:ECB、GCM nonce reuse、Padding Oracle、低 e、共模攻击、长度扩展、时序攻击。
- 了解后量子密码(PQC)发展方向及 NIST 最新选型(ML-KEM、ML-DSA、SLH-DSA)。
- 会用 Python 手动实现 RSA / DH / HMAC / AES-GCM 以理解底层细节。
能力矩阵:
| 能力域 | 入门 | 进阶 | 精通 |
|---|---|---|---|
| 使用库 | AES-GCM 加密一段字符串 | 正确 KDF + AEAD + 证书锁定 | 设计跨语言/跨平台密码协议 |
| 分析题 | CTF 基本 RSA | 侧信道 / padding oracle | 实现自己的 Wycheproof 测试 |
| 协议阅读 | TLS 1.3 大致流程 | HKDF 推导链 | 解读 Signal / Noise Protocol 规范 |
4.2 数论先修知识
4.2.1 模算术基本定理
- 模运算:( a \equiv b \pmod n \Longleftrightarrow n \mid (a-b) )
- 乘法逆元:若 ( \gcd(a,n)=1 ),存在唯一 ( a^{-1} ) 使 ( a \cdot a^{-1} \equiv 1 \pmod n )
- 欧拉函数 ( \varphi(n) ):与 ( n ) 互素的正整数个数;( n=p q ) 则 ( \varphi(n) = (p-1)(q-1) )
4.2.2 费马小定理与欧拉定理
- 费马小定理:若 ( p ) 素、( \gcd(a,p)=1 ),则 ( a^{p-1} \equiv 1 \pmod p )
- 欧拉定理:若 ( \gcd(a,n)=1 ),则 ( a^{\varphi(n)} \equiv 1 \pmod n )
RSA 正确性证明(核心一步):
[ m^{ed} \equiv m^{k\varphi(n)+1} \equiv (m^{\varphi(n)})^k \cdot m \equiv 1^k \cdot m \equiv m \pmod n ]
4.2.3 扩展欧几里得算法
def ext_gcd(a, b): if b == 0: return a, 1, 0 g, x, y = ext_gcd(b, a % b) return g, y, x - (a // b) * y
def modinv(a, n): g, x, _ = ext_gcd(a % n, n) if g != 1: raise ValueError("not invertible") return x % n时间复杂度 ( O(\log \min(a,b)) )。
4.2.4 中国剩余定理 (CRT)
若 ( n_1, \dots, n_k ) 两两互素,( N = \prod n_i ),则方程组
[ \begin{cases} x \equiv a_1 \pmod{n_1} \ \vdots \ x \equiv a_k \pmod{n_k} \end{cases} ]
在 ( \mathbb{Z}_N ) 下有唯一解:
[ x \equiv \sum_{i=1}^k a_i \cdot M_i \cdot M_i^{-1} \pmod N, \quad M_i = N / n_i ]
工程应用:RSA-CRT 优化——分别在 ( \pmod p ) 和 ( \pmod q ) 下计算,总耗时约为一次 ( \pmod{pq} ) 计算的 1/4。
4.2.5 离散对数问题 (DLP)
给定素数 ( p )、生成元 ( g )、( h = g^x \bmod p ),求 ( x ) 为 DLP。当前最好算法:
- 通用:Pollard Rho ( O(\sqrt{q}) )(( q ) 为群阶)
- 对素域:GNFS 子指数时间
- 对 ECC:已知最快 Pollard Rho,仍为 ( O(\sqrt{q}) ) → 256-bit ECC ≈ 3072-bit RSA 安全
4.3 密码学三大支柱
┌──── 对称加密 ───── 机密性(快,密钥共享难) │ AES / ChaCha20 / SM4 │密码学 ──── 非对称加密 ── 密钥交换 / 签名 │ RSA / ECC / Ed25519 │ └──── 哈希函数 ───── 完整性 / 指纹 SHA-256 / SHA-3 / BLAKE2 / SM3组合原则:
- 想要 机密性 + 完整性:使用 AEAD(AES-GCM、ChaCha20-Poly1305)
- 想要 密钥交换 + 机密性:ECDHE + AEAD(TLS 1.3 架构)
- 想要 身份 + 抗否认:数字签名(Ed25519 / ECDSA)
4.4 对称加密
4.4.1 分组密码 vs 流密码
| 类型 | 代表 | 特点 |
|---|---|---|
| 分组密码 | AES-128/192/256、SM4 | 固定块长(128 bit) |
| 流密码 | ChaCha20、Salsa20、RC4(已废) | 生成密钥流 XOR |
4.4.2 AES 算法内部结构
AES-128 共 10 轮,每轮 4 个步骤:
State = 4×4 byte 矩阵 │ 10 × (SubBytes → ShiftRows → MixColumns → AddRoundKey) │ (最后一轮无 MixColumns) ▼CiphertextSubBytes(S-Box 替换)
对状态每字节 ( a ),查 S-Box:首先求其在 GF(( 2^8 )) 上的乘法逆(用不可约多项式 ( x^8+x^4+x^3+x+1 )),再做仿射变换:
[ b_i = a_i \oplus a_{(i+4)\bmod 8} \oplus a_{(i+5)\bmod 8} \oplus a_{(i+6)\bmod 8} \oplus a_{(i+7)\bmod 8} \oplus c_i ]
其中 ( c = 0x63 )。
ShiftRows
第 0 行不动,第 1/2/3 行左移 1/2/3 字节。
MixColumns
每列被视作 GF(( 2^8 )) 上的 4 维向量,乘以矩阵:
[ \begin{pmatrix} 2 & 3 & 1 & 1 \ 1 & 2 & 3 & 1 \ 1 & 1 & 2 & 3 \ 3 & 1 & 1 & 2 \end{pmatrix} ]
乘法在 GF(( 2^8 )) 上进行(模 ( x^8+x^4+x^3+x+1 ))。
AddRoundKey
状态与当前轮密钥按字节异或。
Key Schedule
从 128-bit 主密钥生成 11 个 128-bit 轮密钥。每 4 个 word 生成一个新的 word,用到 RotWord、SubWord(S-Box)和 Rcon 常数。
4.4.3 工作模式深度
| 模式 | 描述 | 安全性 |
|---|---|---|
| ECB | 每块独立加密 | 不安全,明显 pattern(企鹅图实验) |
| CBC | 上一块密文 XOR 下一块明文 | 需要 IV;Padding Oracle 风险 |
| CTR | 计数器 + 密钥流 | 并行,IV 不能重用 |
| GCM | CTR + GHASH 认证 | 推荐,AEAD |
| CCM | CBC-MAC + CTR | 轻量,常用于 WiFi WPA2 |
| XTS | 磁盘加密专用 | BitLocker、LUKS |
| OCB | 单 pass AEAD | 专利到期后可用,高性能 |
| SIV | 抗 nonce reuse | 密钥派生+随机数兼容 |
4.4.4 GCM 深入
GCM(K, IV, P, A): J0 ← IV || 0^31 || 1 (12-byte IV 常见场景) C ← CTR_K(J0+1, P) (counter 从 J0+1 开始) H ← AES_K(0^128) T ← GHASH_H(A || 0* || C || 0* || len(A) || len(C)) ⊕ AES_K(J0) 返回 (C, T)其中 GHASH_H(X) 在 GF(( 2^{128} )) 上是:
[ Y_i = (Y_{i-1} \oplus X_i) \cdot H \pmod{x^{128}+x^7+x^2+x+1} ]
致命误用:同一密钥下重复使用相同 nonce,两段密文 C1 ⊕ C2 = P1 ⊕ P2,同时可恢复认证密钥 H(Forbidden Attack,Joux 2006)。工程补偿:使用 SIV / nonce-random-128bit 或定期轮换密钥。
4.4.5 常见误用全谱
- ECB 加密头像 → 泄露结构
- CBC + 固定 IV → 相同明文 = 相同密文
- GCM 重用 nonce → 灾难级
- Padding Oracle(POODLE、Lucky13)→ 逐字节解密
- MAC-then-Encrypt(SSL 历史)→ 应改为 Encrypt-then-MAC 或 AEAD
4.4.6 Python AEAD 示例
from cryptography.hazmat.primitives.ciphers.aead import AESGCM, ChaCha20Poly1305import os
# AES-GCMkey = AESGCM.generate_key(bit_length=256)aesgcm = AESGCM(key)nonce = os.urandom(12)ct = aesgcm.encrypt(nonce, b"plain", b"aad")pt = aesgcm.decrypt(nonce, ct, b"aad")
# ChaCha20-Poly1305(无 AES-NI 时推荐)key2 = ChaCha20Poly1305.generate_key()chacha = ChaCha20Poly1305(key2)nonce2 = os.urandom(12)ct2 = chacha.encrypt(nonce2, b"plain", b"aad")pt2 = chacha.decrypt(nonce2, ct2, b"aad")4.4.7 Padding Oracle 攻击演示
CBC 解密:( P_i = D_K(C_i) \oplus C_{i-1} )。若服务端对 padding 错误返回不同响应,攻击者逐字节改 C_{i-1} 直至最后一字节 padding 正确(0x01)→ 恢复 D_K(C_i) 最后一字节 → 逐位推完整 block。
def padding_oracle_decrypt_block(iv, block, oracle): intermediate = bytearray(16) for pad in range(1, 17): for guess in range(256): mod_iv = bytearray(16) for k in range(pad - 1): mod_iv[16 - 1 - k] = intermediate[16 - 1 - k] ^ pad mod_iv[16 - pad] = guess if oracle(bytes(mod_iv), block): if pad == 1 and guess == iv[15]: continue intermediate[16 - pad] = guess ^ pad break return bytes(a ^ b for a, b in zip(intermediate, iv))4.5 非对称加密
4.5.1 RSA 加解密推导
- 选素数 ( p, q ),( n = pq )
- 选 ( e ) 与 ( \varphi(n) = (p-1)(q-1) ) 互素(常取 65537)
- ( d = e^{-1} \mod \varphi(n) )
- 加密:( c = m^e \bmod n )
- 解密:( m = c^d \bmod n )
密钥长度推荐:当前 ( n \geq 3072 \text{ bit} )(NIST SP 800-57,等效 128-bit 安全);2030 年后建议 4096-bit。
4.5.2 RSA-OAEP 填充
直接 RSA 是确定性的(相同明文→相同密文,易被选择密文攻击),工程上须使用 OAEP:
seed (k0 bit 随机) │ ▼maskedDB = DB ⊕ MGF(seed, k - k0)maskedSeed = seed ⊕ MGF(maskedDB, k0)EM = 0x00 || maskedSeed || maskedDBm_int = OS2IP(EM)c = m_int^e mod n其中 MGF 基于 SHA 构造。OAEP 提供 IND-CCA2 安全。
4.5.3 RSA 经典攻击
| 攻击 | 条件 |
|---|---|
| 低 e 低 m | e=3 且 m < n^{1/3},直接开 3 次方 |
| 广播攻击 | 同一 m 被 3 个不同 n 加密(e=3)→ CRT 合并 → 开方 |
| 共模攻击 | 同一 m 用相同 n 但不同 e₁,e₂ 加密;若 gcd(e₁,e₂)=1 可恢复 |
| Wiener | d < n^{1/4}/3 时通过连分数恢复 d |
| Boneh-Durfee | d < n^{0.292} 时格攻击 |
| Coppersmith | 已知 p 的高 N/4 比特,可多项式恢复 |
| Bleichenbacher 1998 | PKCS#1 v1.5 填充 oracle → 选择密文攻击 |
| ROBOT (CVE-2017-6168) | Bleichenbacher 变体,现代 TLS |
4.5.4 手写 RSA(教学用途)
import secretsfrom sympy import isprime, nextprime
def gen_prime(bits): while True: n = secrets.randbits(bits) | (1 << (bits - 1)) | 1 if isprime(n): return n
def gen_rsa(bits=2048): p = gen_prime(bits // 2) q = gen_prime(bits // 2) n = p * q phi = (p - 1) * (q - 1) e = 65537 d = pow(e, -1, phi) return (n, e), (n, d)
pub, priv = gen_rsa(2048)m = int.from_bytes(b"hello", 'big')c = pow(m, pub[1], pub[0])m2 = pow(c, priv[1], priv[0])print(m2.to_bytes(5, 'big'))4.5.5 椭圆曲线密码(ECC)
曲线方程(Weierstrass 形式):
[ y^2 = x^3 + ax + b \quad \text{over } \mathbb{F}_p ]
曲线上的点与”无穷远点” ( \mathcal{O} ) 构成 Abel 群。
点加运算(( P \neq Q ))
[ \lambda = (y_Q - y_P) / (x_Q - x_P), \quad x_R = \lambda^2 - x_P - x_Q, \quad y_R = \lambda(x_P - x_R) - y_P ]
倍点(( P = Q ))
[ \lambda = (3 x_P^2 + a) / (2 y_P), \quad \text{再用上式} ]
标量乘 ( kP )(Double-and-Add)
def point_mul(k, P, curve): R = None # 表示无穷远 Q = P while k > 0: if k & 1: R = point_add(R, Q, curve) Q = point_double(Q, curve) k >>= 1 return R常用曲线:
| 曲线 | 说明 |
|---|---|
| secp256k1 | 比特币 |
| secp256r1 (P-256) | NIST 推荐 |
| Curve25519 / Ed25519 | DJB 设计,抗侧信道,TLS 1.3 |
| BLS12-381 | zk-SNARK / 聚合签名 |
4.5.6 Diffie-Hellman / ECDH
公共参数:大素数 p、生成元 gAlice: a 随机, A = g^a mod pBob: b 随机, B = g^b mod pAlice 收到 B → 计算 K = B^a mod pBob 收到 A → 计算 K = A^b mod p中间人攻击(MitM):需要认证 → TLS 证书、SSH 指纹、QR 码比较。
前向安全:一次性临时密钥(DHE / ECDHE)→ 私钥泄露也不能解过去的流量。
Logjam 回顾:512-bit DH 可被数域筛预计算攻破;2015 后浏览器强制 ≥ 1024,2020 后强制 ≥ 2048,TLS 1.3 禁用 DH < 2048 并推荐 ECDHE。
4.5.7 手写 DH
import secretsp = int("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1..."[:770], 16) # RFC 3526 2048-bitg = 2a = secrets.randbelow(p - 2) + 2A = pow(g, a, p)b = secrets.randbelow(p - 2) + 2B = pow(g, b, p)K_A = pow(B, a, p)K_B = pow(A, b, p)assert K_A == K_B4.5.8 数字签名算法
| 算法 | 基础 | 特点 |
|---|---|---|
| RSA-PKCS#1 v1.5 | RSA | 不可变填充,谨慎使用 |
| RSA-PSS | RSA | 推荐,概率性填充 |
| DSA | 素域 DLP | FIPS 186 |
| ECDSA | ECC DLP | 短签名,TLS 常用 |
| Ed25519 | EdDSA on Curve25519 | 确定性、抗侧信道 |
| SM2 | ECC over Fp | 中国国密 |
| Dilithium (ML-DSA) | 格 Module-LWE | 后量子 |
| FALCON | NTRU 格 | 紧凑签名 |
| SLH-DSA (SPHINCS+) | 哈希 | 无需数学假设 |
4.5.9 ECDSA 随机数事故
ECDSA:对消息 ( m ) 签名
[ s = k^{-1} (H(m) + r \cdot d) \bmod n, \quad r = (kG).x ]
若 ( k ) 被重用两次(不同消息 ( m_1 \neq m_2 )),则:
[ d = \frac{s_1 H(m_2) - s_2 H(m_1)}{r(s_2 - s_1)} \bmod n ]
真实事故:
- Sony PS3:固件签名 k 恒为常数 → 私钥被 fail0verflow 泄露
- Android Wallet 2013:
SecureRandombug → 大量比特币被窃 - TinyBTC 等 IoT 设备的熵源不足
防御:Ed25519 / 确定性 ECDSA(RFC 6979)。
4.6 哈希函数
4.6.1 安全性质
- 抗原像:给定 h(x),难以找 x
- 抗第二原像:给定 x,难以找 x’ 使 h(x’)=h(x)
- 抗碰撞:难以找两个 x ≠ y 使 h(x)=h(y)(生日界 ( 2^{n/2} ))
4.6.2 主流算法
| 算法 | 输出 | 状态 |
|---|---|---|
| MD5 | 128 bit | 碰撞已破(10 秒内) |
| SHA-1 | 160 bit | 碰撞已破(SHAttered, 2017) |
| SHA-256 / 512 | 256 / 512 bit | 推荐,Merkle-Damgård 结构 |
| SHA-3(Keccak) | 可变 | 推荐,海绵结构 |
| BLAKE2 / BLAKE3 | 可变 | 高性能、树并行 |
| SM3 | 256 bit | 国密 |
4.6.3 Merkle-Damgård 构造
Message → Padding → 分块 M_1, M_2, ..., M_t │IV → f(IV, M_1) = H_1 → f(H_1, M_2) = H_2 → ... → H_t │ 附加长度 → Final Hash其中压缩函数 ( f: {0,1}^n \times {0,1}^b \to {0,1}^n ) 是核心。
长度扩展攻击:给定 H(secret || m) 和 len(secret),可计算 H(secret || m || pad || m2) 而无需 secret。受影响:MD5、SHA-1、SHA-2。不受影响:SHA-3、BLAKE2、HMAC。
4.6.4 Sponge 构造(SHA-3)
absorb 阶段: S = 0 for block in M: S[0:r] ^= block S = Keccak-f(S)
squeeze 阶段: out = S[0:r] while 需要更多: S = Keccak-f(S) out += S[0:r]抗长度扩展(因 capacity c 部分不向外暴露)。
4.6.5 HMAC
[ \text{HMAC}(K, m) = H\big( (K’ \oplus \text{opad}) ,|, H((K’ \oplus \text{ipad}) ,|, m) \big) ]
其中 ( K’ ) 是密钥的规整化(过长则先 hash 再 pad)。抗长度扩展、抗某些 H 内部弱点。
4.6.6 口令哈希(慢哈希)
bcrypt / scrypt / Argon2 ← 推荐(加盐 + 迭代 + 内存硬)PBKDF2-HMAC-SHA256 ← FIPS 合规但易被 GPU 加速MD5 / SHA1 / SHA256 ← 裸 hash 用于口令 = 错误推荐:Argon2id(password, salt, m=64MB, t=3, p=4);bcrypt cost≥12;scrypt N=2^17。
为何内存硬:GPU 擅长 SIMD 但内存带宽相对有限;Argon2 要求大块内存访问,使攻击者无法利用成千上万 GPU 线程。
4.7 MAC 与数字签名对照
| 机制 | 密钥 | 提供 | 不可否认 |
|---|---|---|---|
| MAC(HMAC) | 对称 | 完整性 + 源认证 | ❌ |
| 数字签名(RSA/ECDSA) | 非对称 | 完整性 + 源认证 | ✅ |
4.7.1 时序攻击
# 不安全if mac_received == hmac(key, msg): ...比较字符串的 == 一旦遇到不匹配字节立即返回,攻击者可通过耗时差距逐字节猜 MAC。
修复:hmac.compare_digest(常量时间)。
import hmac as _hmacif _hmac.compare_digest(mac_received, _hmac.new(key, msg, "sha256").digest()): ...4.7.2 HMAC 抗攻击非形式化证明
假设底层哈希 H 是 PRF(或接近);HMAC 的两次嵌套(inner + outer)等价于 NMAC,而 NMAC 在压缩函数为 PRF 的情况下是可证明安全的 MAC(Bellare, 1996)。
4.8 PKI、证书与现代协议组合
4.8.1 证书链
根 CA (离线) ──签发──▶ 中间 CA ──签发──▶ 服务器证书 │ 浏览器/OS 信任根 CA- X.509 证书字段:Subject、Issuer、Public Key、SAN、Extensions
- 吊销:CRL(列表)/ OCSP(在线查询)/ OCSP Stapling
- CAA DNS 记录:限制哪家 CA 可签发本域证书
- CT(Certificate Transparency):强制公开所有证书
4.8.2 Signal 端到端加密
身份密钥 IK + 签名预共享密钥 SPK + 一次性预共享密钥 OPK→ X3DH 协商根密钥→ Double Ratchet 每条消息演进→ 前向安全 + 后向安全- X3DH:Alice 用 Bob 的 IK/SPK/OPK 做 4 次 DH → 合并为共享密钥
- Double Ratchet:DH Ratchet(每次换公钥)+ Chain Key Ratchet(KDF 链,每条消息推进)
4.8.3 TLS 1.3 密码套件
TLS_AES_128_GCM_SHA256TLS_AES_256_GCM_SHA384TLS_CHACHA20_POLY1305_SHA256TLS_AES_128_CCM_SHA256 (嵌入式)注意此时套件只表示 对称算法 + 哈希,不再涵盖密钥交换(交换算法写在 supported_groups 扩展里)。
4.8.4 JWT 常见坑
alg: none绕过(服务端未校验 alg)- RS256 → HS256 混淆(服务端接受两种算法;攻击者用公钥当 HMAC 密钥)
kid注入(SQL / 路径遍历)jku/x5u外连攻击者 JWKS- 弱 secret(
secret、123456)被 hashcat 秒破
4.8.5 HKDF 手写
import hashlib, hmac
def hkdf_extract(salt: bytes, ikm: bytes, hash_name='sha256') -> bytes: if salt is None: salt = b'\x00' * hashlib.new(hash_name).digest_size return hmac.new(salt, ikm, hash_name).digest()
def hkdf_expand(prk: bytes, info: bytes, length: int, hash_name='sha256') -> bytes: hash_len = hashlib.new(hash_name).digest_size blocks = (length + hash_len - 1) // hash_len okm, t = b'', b'' for i in range(1, blocks + 1): t = hmac.new(prk, t + info + bytes([i]), hash_name).digest() okm += t return okm[:length]4.9 后量子密码(PQC)
4.9.1 量子威胁
- Shor 算法(1994):多项式时间解整数分解与离散对数 → RSA/DH/ECC 全崩
- Grover 算法:把对称加密暴力搜索从 ( 2^n ) 降到 ( 2^{n/2} ) → AES-128 → 约 64-bit 强度;推荐 AES-256
4.9.2 NIST PQC 选中算法(FIPS 203/204/205, 2024)
| 类型 | 算法 | 基础难题 |
|---|---|---|
| KEM(密钥封装) | ML-KEM(原 Kyber) | Module-LWE |
| 签名 | ML-DSA(Dilithium) | Module-LWE/SIS |
| 签名 | SLH-DSA(SPHINCS+) | 哈希 |
| 签名 | FALCON | NTRU |
4.9.3 Kyber (ML-KEM) 核心
基于 Module-LWE 困难问题:给定均匀 ( A )、短向量 ( s, e ),( t = A s + e );已知 ( (A, t) ) 难以恢复 ( s )。
KeyGen: A ← random 3×3 polynomial matrix over R_q (s, e) ← small t = A s + e pk = (A, t), sk = s
Encaps(pk): m ← random 256-bit r, e1, e2 ← small u = A^T r + e1 v = t^T r + e2 + ⌈q/2⌋ · Decode(m) return c=(u,v), K = H(m)
Decaps(sk, c): w = v - s^T u # ≈ ⌈q/2⌋ · Decode(m) + small noise m' = Round(w) return K = H(m')其中 ( R_q = \mathbb{Z}_q[X]/(X^{256}+1) ),( q = 3329 )。
4.9.4 工程部署
- Chrome 116+ (2023):TLS 中混合
X25519+Kyber768 - Cloudflare, Google:内部 RPC 已普遍启用 PQC
- OpenSSH 9.0+ (2022):默认启用
sntrup761x25519-sha512@openssh.com混合 KEM - 签名尚未大规模部署,因尺寸较大(Dilithium5 签名 4627 B)
4.10 CVE 深度复盘汇总
4.10.1 Heartbleed(已在 Ch02 详述)
OpenSSL heartbeat 越界读——提醒 机密性依赖内存安全;后续 OpenBSD fork LibreSSL。
4.10.2 ROBOT (CVE-2017-6168 等)
Bleichenbacher 1998 攻击的变体,现代 TLS 服务器实现对 RSA-PKCS#1 v1.5 填充错误响应差异仍然存在。攻击者构造选定密文,基于响应差异逐比特恢复会话密钥。修复:TLS 1.3 废弃 RSA 密钥交换。
4.10.3 DUHK (2017)
Fortinet FortiOS 使用固定种子初始化 ANSI X9.31 RNG → VPN 会话密钥可被离线恢复。提醒:硬编码 RNG 种子 = 灾难。
4.10.4 Curveball (CVE-2020-0601)
Windows crypt32.dll 在验证 ECC 签名时允许攻击者指定”生成元”而不校验 → 能伪造任意 CA 的签名。修复:强制生成元为曲线标准点。
4.10.5 Debian OpenSSL PRNG 缺陷 (2006-2008)
Debian 维护者在审计时误删 MD_Update 调用 → 只有 PID 作为熵源 → 全球大量 SSH 密钥可枚举(约 32768 个)。教训:密码学代码不要随便”清理”。
4.11 工具速查
| 任务 | 工具 |
|---|---|
| 证书查看 | openssl x509 -in cert.pem -noout -text |
| 私钥生成 | openssl genpkey -algorithm ED25519 |
| 哈希计算 | sha256sum / certutil -hashfile(Win) |
| 口令破解 | hashcat -m 1800 hash.txt wordlist.txt |
| CTF crypto | RsaCtfTool / sage / gmpy2 / pycryptodome |
| TLS 扫描 | testssl.sh / sslscan / nmap --script ssl-* |
| JWT 解析 | https://jwt.io / jwt_tool.py |
| 格攻击 | fpylll / sagemath / cvp |
| PQC 实验 | liboqs / oqs-provider(OpenSSL) |
4.12 练习题
- 证明 RSA 的解密正确性(含 ( \gcd(m,n) \neq 1 ) 的情形)。
- 给定 ( n = 97 \cdot 89 )、( e = 7 ),求 ( d ) 及 ( \varphi(n) )。
- 写一个 CTF 小脚本:已知
(n, e=3, c),( m < n^{1/3} ),直接开三次方恢复明文。 - 解释 GCM 为什么禁止在同一密钥下重复 nonce;若不小心重用了会泄露什么?
- 用 HMAC-SHA256 实现 TOTP(RFC 6238)。
- 对比 PBKDF2 与 Argon2 在 GPU 上的攻击开销估算。
- 解读 Ed25519 的签名为什么是确定性的,这对侧信道有什么好处?
- 用
python-fpylll实现一个小尺寸 Coppersmith 攻击:已知 RSAn,e=3, 明文的高位,恢复明文低位。 - 复现一个 Padding Oracle 的小 demo(自建 Flask 服务返回不同 HTTP code)。
- 阅读 TLS 1.3 HKDF 推导链,写出从 Handshake Secret 到 Client Application Traffic Secret 的 Label 链。
参考答案要点
- 一般情形用 CRT:
m^ed ≡ m (mod p)和m^ed ≡ m (mod q)合并。 - ( \varphi(n) = 96 \cdot 88 = 8448 ),( d = 7^{-1} \bmod 8448 ) 用扩欧。
m = iroot(c, 3)[0](gmpy2.iroot)。- 会让两段密文
C1⊕C2 = P1⊕P2,且可恢复 GHASH 认证密钥 H。 - TOTP = HOTP(K, ⌊T/30⌋),HOTP 基于 HMAC-SHA1 + 动态截断。
- Argon2 内存硬 → GPU 的高并发 ALU 优势失效;PBKDF2 可 GPU 高并发加速 1000x+。
- k 由
H(prefix || m)确定,不依赖 RNG → 不会因弱 RNG 重用 k;侧信道上减少一个泄露源。 - 构造多项式 ( f(x) = (m_{high} + x)^3 - c ) 在 ( \bmod n ) 下解小根。
Derive-Secret(Handshake Secret, "c hs traffic", ClientHello..ServerHello)得 CHTS;Derive-Secret(Master Secret, "c ap traffic", ClientHello..server Finished)得 CATS。
4.13 面试高频考点(附参考答案)
Q1:为什么 ECB 不安全?举图像例子。
- 相同明文块 → 相同密文块;加密企鹅图仍能看出轮廓。
Q2:AES-GCM 的 nonce 为什么不能重用?
- CTR 密钥流决定于 (K, nonce),重用会泄露 P1⊕P2;同时 GHASH 认证密钥 H 可被恢复 → 任意伪造。
Q3:HMAC 为什么比 H(secret || m) 安全?
- 抗长度扩展;Bellare 可证在 H 的压缩函数为 PRF 时 HMAC 是 PRF。
Q4:Diffie-Hellman 为什么会被中间人攻击?如何防御?
- 无身份认证 → 中间人与双方各协商一把密钥;防御:证书、TOFU、口令短语比对、QR 码。
Q5:RSA 的 e=3 攻击场景?
- m < n^{1/3} 直接开方;广播攻击(3 个不同 n 加密同一 m,CRT 合并);OAEP 填充不当也可能被 Manger 攻击。
Q6:什么是 Padding Oracle?举 CBC 例子。
- 服务端对 padding 错误返回可区分响应 → 逐字节解密。
Q7:bcrypt vs Argon2,谁更推荐?为什么?
- Argon2(内存硬 + 可并行参数),尤其是 Argon2id。
Q8:数字签名和 MAC 的本质区别?
- 签名用非对称密钥 → 可公开验证 + 不可否认;MAC 用对称密钥 → 任一方都能生成。
Q9:TLS 1.3 相对 1.2 有什么安全升级?
- 去 RSA 密钥交换(强制前向安全);禁用 CBC、RC4、SHA1、压缩;握手 1-RTT;0-RTT(可选);禁止重协商。
Q10:后量子密码工程迁移的主要挑战?
- 密钥/签名尺寸变大(3~30×);CPU 开销增加;协议握手包过大 → TLS 分片;硬件 HSM 适配;混合部署过渡期风险。
4.14 补充:零知识证明与同态加密
4.14.1 零知识证明 (ZKP) 基础
三性质:
- 完备性:诚实证明者可让诚实验证者确信真命题。
- 可靠性:作弊证明者无法以高概率让验证者相信假命题。
- 零知识性:验证者除了命题为真外学不到额外信息。
Schnorr 离散对数零知识证明(交互式):
Prover 知道 x 使 y = g^x 1. Prover: 选 r 随机,计算 t = g^r,发 t 2. Verifier: 选挑战 c 随机 3. Prover: s = r + c·x (mod q),发 s 4. Verifier: 校验 g^s == t · y^cFiat-Shamir 启发式:把挑战 ( c ) 换成 ( c = H(g, y, t) ) 即可非交互化。
4.14.2 zk-SNARK / zk-STARK 对比
| 属性 | zk-SNARK (Groth16) | zk-STARK |
|---|---|---|
| 依赖 | 双线性对 + 椭圆曲线 | 哈希 + FRI |
| 抗量子 | 否 | 是 |
| 可信设置 | 需要 trusted setup | 无 |
| 证明大小 | ~200 B | ~100 KB |
| 验证速度 | 毫秒级 | 较慢 |
4.14.3 同态加密
- 部分同态(Paillier:加法)
- 近似同态(BFV / BGV / CKKS)
- 全同态(FHE,Gentry 2009)
- 开源库:Microsoft SEAL、OpenFHE、Zama TFHE-rs
- 典型应用:隐私 ML 推理、密文数据库查询
Paillier 加法同态 直观:
[ E(m_1) \cdot E(m_2) = E(m_1 + m_2), \quad E(m)^k = E(k m) ]
4.15 补充:常见误用 checklist
- 口令不加盐、不慢哈希 → 使用 Argon2id
- 对称加密不带认证 → 使用 AEAD
- CBC + 可预测 IV → IV 随机 + Encrypt-then-MAC 或 AEAD
- ECDSA k 不随机 / 可重用 → 使用 Ed25519 或 RFC 6979
- RSA 不填充 / v1.5 填充 → 使用 OAEP (加密) 或 PSS (签名)
- 自定义密钥派生 → 使用 HKDF
- 口令直接作为 AES 密钥 → PBKDF2/Argon2 派生
- HMAC 比较用
==→ 使用hmac.compare_digest - JWT 信任 alg 字段 → 服务端固定 alg 白名单
- TLS 证书未 pin → 高敏感应用启用证书 pin / CT 校验
- DH 参数 < 2048 bit → ECDHE 或 ≥ 3072-bit DH
- 日志中输出密钥/Token → 关键字过滤 + 最小化日志
4.16 补充:侧信道攻击
4.16.1 时序攻击
- RSA:
exp过程中根据d每一位是否为 1 执行乘法 → 耗时差 - 对比:AES T-table 实现中数组访问命中不同缓存行 → CacheAttack
4.16.2 功耗分析
- SPA (Simple Power Analysis):单次曲线即区分 “乘” vs “平方”
- DPA (Differential Power Analysis):统计分析,需要成千次测量
4.16.3 故障注入
- 电压毛刺 / 时钟毛刺 → 让 CPU 跳过一条指令
- 经典:BellcoreAttack,在 RSA-CRT 中注入错误使 ( s_p ) 错误 → 恢复 ( p )
4.16.4 缓存侧信道
- Flush+Reload:清 cache → 等受害者访问 → 重加载观察耗时
- Prime+Probe:填 cache → 受害者访问 → 重测
- 应用:KAISER / KPTI 防御 Meltdown 类攻击;RSA/AES 常数时间实现
4.16.5 防护
- 常数时间算法(
openssl的BN_consttime、libsodium) - 硬件 HSM / SE(Secure Element)
- 随机化 masking、shuffling
- 指令级:
crc32、aesenc的常数时间硬件支持
4.17 补充:随机数生成
4.17.1 熵源
- 硬件:RDRAND、RDSEED(Intel)、ARMv8 RNDR、TPM RNG
- 软件:中断计时、输入事件、磁盘延迟
- Linux:
getrandom(2)→/dev/urandom(2.6 后合并熵池,现代正确实践)
4.17.2 CSPRNG 构造
- HMAC-DRBG / Hash-DRBG / CTR-DRBG(NIST SP 800-90A)
- ChaCha20-DRBG(Linux 5.17+)
4.17.3 事故教训
- 已提 DUHK、Debian OpenSSL、Android SecureRandom
- CVE-2023-20900 VMware CSP RNG 默认种子 → 虚机镜像批量使用同一种子 → TLS 密钥可碰撞
4.18 补充:国密算法速览
- SM2:基于 ECC over F_p256(国产素数曲线)的签名、密钥交换、加密。国标 GB/T 32918。
- SM3:256-bit 哈希,结构类似 SHA-256,但带不同布尔函数。
- SM4:128-bit 分组对称加密,32 轮非线性 Feistel。
- SM9:基于身份的密码学(IBE / IBS)。
- ZUC:LTE/5G 层序列密码。
合规侧:等保 2.0 / 密码法要求关键系统使用国密,金融、政务尤其严格。
4.18.1 SM4 Python 示例
from gmssl import sm4crypt_sm4 = sm4.CryptSM4()key = b'0123456789ABCDEF'crypt_sm4.set_key(key, sm4.SM4_ENCRYPT)data = crypt_sm4.crypt_ecb(b'hello world12345')生产不要用 ECB,改用 CBC / GCM 模式。
4.19 补充:实战场景与选型表
4.19.1 文件/数据库加密
- 字段级:AES-256-GCM,每条记录独立 nonce + AAD=表名/列名
- 全盘:LUKS2 + Argon2id;BitLocker + TPM;FileVault
- KMS:AWS KMS / GCP Cloud KMS / HashiCorp Vault Transit
- 包裹密钥 (DEK + KEK):数据用 DEK 加密,DEK 用 KEK(由 KMS 管理)加密
4.19.2 传输加密
- Web:TLS 1.3(或强制 TLS 1.2 with PFS cipher suites)
- VPN:WireGuard > OpenVPN/IKEv2
- gRPC:mTLS + SPIFFE ID
- Kafka / Redis:支持 SASL_SSL 或 TLS over TCP
4.19.3 密钥管理
- 切勿把密钥硬编码进代码
- 生产:KMS / HSM / Vault
- 开发:环境变量 +
dotenv,注意不提交到 Git - 轮换:定期 + 异常时强制;需要支持过渡期双密钥验证
4.19.4 密码学库选择
| 语言 | 推荐库 |
|---|---|
| Python | cryptography(高层 API),避免 pycrypto(已弃) |
| Go | crypto/*(官方) + golang.org/x/crypto |
| Rust | ring、rustcrypto 组织、age-encryption.org/v1 |
| Java | Bouncy Castle、JCA/JCE |
| C/C++ | libsodium、OpenSSL 3、BoringSSL |
| Node.js | node:crypto、libsodium-wrappers、@peculiar/webcrypto |
4.20 补充:CTF Crypto 套路整理
| 题型 | 关键词 | 工具 |
|---|---|---|
| RSA 小 e | e=3, 明文短 | gmpy2.iroot |
| RSA 共模 | 同 n,不同 e | extended_gcd |
| RSA Wiener | d 过小 | owiener |
| RSA Coppersmith | 部分已知 | sage .small_roots() |
| DLP 小范围 | p-1 平滑 | Pollard p-1 / rho |
| 已知明密文 | AES/DES 参数未知 | 暴力猜模式 |
| LCG 预测 | 随机数题 | sage 格方法 |
| 椭圆曲线反常 | #E(F_p) = p | Smart 攻击 |
| 哈希碰撞 | MD5 collision | hashcash, chosen-prefix |
| 格密码 | LLL, BKZ | fpylll / sage |
4.21 补充:常见问答速记
- AEAD = Authenticated Encryption with Associated Data:在加密同时验证密文 + 关联数据(AAD)未被篡改。
- IND-CPA vs IND-CCA2:前者抵抗选择明文攻击,后者额外抵抗自适应选择密文攻击。工业界默认目标是 IND-CCA2。
- Forward Secrecy vs Post-Compromise Security:前向安全保障”私钥泄露不解过去”;后妥协安全(PCS/self-healing)保障”妥协之后可恢复”。Signal 的 Double Ratchet 同时提供两者。
- Random Oracle Model:在证明中把哈希函数当理想均匀随机函数,方便分析;工程上不存在真正的 Random Oracle。
- Standard Model:不依赖随机预言机;证明更强,但算法往往更复杂或效率更差。
4.22 补充:协议合规性要点
- FIPS 140-3:美政采购门槛;模块化认证 + 受控的算法清单。
- Common Criteria (CC):国际通用标准,EAL 等级评估。
- PCI-DSS:支付行业,对 KMS、传输加密、日志有明确要求。
- GDPR / 《个人信息保护法》:加密是”合理技术措施”的核心之一。
- CNCERT / CCRC 国密改造:国内金融、政务数据传输需 SM2/SM3/SM4 合规。
4.23 延伸阅读
历史经典 CVE 速查(密码学相关)
| CVE | 描述 |
|---|---|
| CVE-2014-0160 | OpenSSL Heartbleed 内存泄露 |
| CVE-2017-3225 | DUHK 固定 RNG seed |
| CVE-2017-13099 | ROBOT (Bleichenbacher 复活) |
| CVE-2018-0737 | OpenSSL RSA 密钥生成定时通道 |
| CVE-2019-1543 | OpenSSL ChaCha20-Poly1305 16KB 缓冲溢出 |
| CVE-2020-1968 | Raccoon Attack 对 TLS-DH 的定时攻击 |
| CVE-2022-3602 / CVE-2022-3786 | OpenSSL X.509 punycode 缓冲溢出 |
| CVE-2023-0286 | OpenSSL X.400 类型混淆 |
教材
- Jean-Philippe Aumasson,《Serious Cryptography》
- Ferguson, Schneier, Kohno,《Cryptography Engineering》
- Katz, Lindell,《Introduction to Modern Cryptography (3rd Ed.)》
- Dan Boneh, Victor Shoup,《A Graduate Course in Applied Cryptography》(免费在线)
- Handbook of Applied Cryptography(HAC,免费 PDF)
在线课程
- Dan Boneh,Cryptography I / II(Coursera / Stanford)
- CryptoHack:https://cryptohack.org/
- Cryptopals Challenges:https://cryptopals.com/
标准 / 规范
- NIST SP 800-57 Part 1 Rev. 5 - Key Management
- NIST FIPS 203 (ML-KEM), 204 (ML-DSA), 205 (SLH-DSA)
- RFC 5869 (HKDF), RFC 8446 (TLS 1.3), RFC 8439 (ChaCha20-Poly1305)
- RFC 6979 (Deterministic ECDSA)
- ISO/IEC 11770 - Key Management
论文 / 博客
- Aviram et al., DROWN: Breaking TLS Using SSLv2, USENIX Security 2016
- Adrian et al., Imperfect Forward Secrecy (Logjam), CCS 2015
- Bernstein, Curve25519: new Diffie-Hellman speed records, PKC 2006
- Biryukov, Dinu, Khovratovich, Argon2, PHC winner 2015
- Real-World Cryptography(Wong, Manning 2021)
- Cryptography Dispatches(Filippo Valsorda newsletter)
如果這篇文章對你有幫助,歡迎分享給更多人!
部分資訊可能已經過時





















