32. 加密与安全基础

系统梳理密码学核心构件:机密性/完整性/认证/不可否认四大安全目标、对称加密 AES 与分组密码工作模式、非对称加密 RSA 与 ECC 的数学基础、哈希函数的抗碰撞与雪崩效应、HMAC 与密钥派生、数字签名与证书链,以及 TLS 握手的完整流程与工程落地避坑。

1. 安全目标与威胁模型

1.1 四大安全目标

目标含义主要手段
机密性 Confidentiality内容不被未授权者读取对称/非对称加密
完整性 Integrity内容未被篡改哈希、MAC
认证 Authentication确认对方身份数字签名、证书
不可否认 Non-repudiation发送方无法抵赖数字签名

1.2 威胁模型

密码学安全性必须相对攻击者能力来定义,常见模型由弱到强:

  • 唯密文攻击:只有密文。
  • 已知明文攻击:掌握若干明文密文对。
  • 选择明文攻击(CPA):可任意加密,观察密文。
  • 选择密文攻击(CCA):还可解密任意密文,现代协议要求能抗 CCA。

核心原则(Kerckhoffs 原则):算法公开、安全性只依赖密钥保密。任何「靠算法保密」的方案都不可靠。

2. 对称加密

2.1 基本模型

对称加密用同一把密钥加解密,速度极快(GB/s 级),适合加密数据本身。

密文 = E(K, 明文)
明文 = D(K, 密文)

2.2 AES

AES 是当今标准的分组密码,分组长度固定 128 位,密钥长度 128/192/256 位。其内部轮函数包含四个步骤:

  • SubBytes:字节代换(S 盒,非线性)。
  • ShiftRows:行移位(扩散)。
  • MixColumns:列混合(扩散)。
  • AddRoundKey:与轮密钥异或。

128 位密钥执行 10 轮,256 位执行 14 轮。设计精髓:混淆(非线性)+ 扩散(雪崩),使密文与明文、密钥的关系高度复杂。

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os

key = AESGCM.generate_key(bit_length=256)
aes = AESGCM(key)
nonce = os.urandom(12)                       # 每次加密必须唯一
ct = aes.encrypt(nonce, b"secret", b"aad")   # 返回 密文+认证标签
pt = aes.decrypt(nonce, ct, b"aad")

2.3 密钥分发难题

对称加密的死穴:n 个用户两两通信需要 n(n-1)/2 把密钥,且如何安全地把密钥送到对方手里?这正是非对称加密要解决的问题。

3. 分组密码工作模式

3.1 常见模式

模式特点是否推荐
ECB每块独立加密,相同明文出相同密文禁止使用
CBC链接前一块密文,需随机 IV可用但易被 padding oracle 攻击
CTR把分组密码当流密码,可并行可用,nonce 绝不可重用
GCMCTR + GMAC,加密同时认证首选(AEAD)
CCMCTR + CBC-MAC资源受限场景

3.2 ECB 的经典缺陷

ECB 下相同明文块产生相同密文块,加密位图时轮廓清晰可见:

明文块: [A][A][B][B]
ECB密文: [X][X][Y][Y]   ← 泄露了重复模式

3.3 选择建议

新项目一律用 AEAD 模式(AES-GCM 或 ChaCha20-Poly1305),一次调用同时提供机密性与完整性,避免「加密了但没认证」这类经典漏洞。密钥随机、nonce 唯一、认证标签必须校验。

4. 非对称加密

4.1 数学基础

非对称加密使用成对的公私钥,安全性依赖数学难题:

  • RSA:大整数分解难题。n = p × q,公钥 (n, e),私钥 (n, d),满足 e·d ≡ 1 (mod φ(n))。
  • ECC:椭圆曲线离散对数难题。256 位 ECC 安全性约等于 3072 位 RSA,密钥更短、更快。
  • DH/ECDH:密钥交换,双方在不安全信道上协商出共享密钥。

4.2 加密与签名是两个方向

加密:  公钥加密 → 私钥解密   (谁都能加密,只有我解密)
签名:  私钥签名 → 公钥验签   (只有我能签,谁都能验证)

4.3 代码示例

from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes

priv = rsa.generate_private_key(public_exponent=65537, key_size=2048)
pub = priv.public_key()

# 加密(只能加密很短的数据,实际用混合加密)
ct = pub.encrypt(b"hi", padding.OAEP(mgf=padding.MGF1(hashes.SHA256()),
                                    algorithm=hashes.SHA256(), label=None))

# 签名
sig = priv.sign(b"message", padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
                                        salt_length=padding.PSS.MAX_LENGTH),
                hashes.SHA256())
pub.verify(sig, b"message", padding.PSS(...), hashes.SHA256())

4.4 混合加密

非对称加密慢且长度受限,实际协议(TLS)采用混合加密:

① 用非对称密钥交换协商出一把临时对称密钥
② 后续数据全部用对称加密(AES-GCM)传输

既解决了密钥分发,又保留了对称加密的性能。

5. 哈希函数

5.1 三大性质

  • 抗原像性:给定 h,无法反推出 m。
  • 抗第二原像性:给定 m1,找不到 m2 ≠ m1 使 h(m1) = h(m2)。
  • 抗碰撞性:找不到任意一对 m1 ≠ m2 使哈希相同(生日攻击约束下需要 2^(n/2) 复杂度)。

5.2 雪崩效应

输入改变 1 个比特,输出约一半比特翻转:

SHA256("hello")  = 2cf24dba5fb0a30e26e83b2ac5b9e29e...
SHA256("hellp")  = b7e1b3c3d4e0b3d8b1e3...(完全不同)

5.3 算法选型

算法输出长度状态
MD5128 位已破解,禁止用于安全
SHA-1160 位已碰撞(SHAttered),禁止
SHA-256/512256/512 位安全,广泛使用
SHA-3可变海绵结构,与 SHA-2 互补
BLAKE3可变极快,非密码学场景友好

注意:哈希 ≠ 加密。哈希不可逆,不能「解密哈希」,只能暴力或彩虹表碰撞。

5.4 密码存储

# 错误:直接存哈希 → 彩虹表秒破
# 正确:加盐 + 慢哈希(bcrypt / scrypt / Argon2)
import bcrypt
hashed = bcrypt.hashpw(b"password", bcrypt.gensalt(rounds=12))
assert bcrypt.checkpw(b"password", hashed)

加盐防止彩虹表与相同密码同哈希;慢哈希(可调代价参数)抬高暴力破解成本。

6. 消息认证码与密钥派生

6.1 HMAC

HMAC 用密钥 + 哈希构造消息认证码,验证完整性与来源,但不提供不可否认性(双方共享密钥,谁都能算):

HMAC(K, m) = H((K ⊕ opad) || H((K ⊕ ipad) || m))

工程要点:比较 MAC 必须用常数时间比较,普通 == 会因短路提前返回而泄露信息(时序攻击)。

import hmac
hmac.compare_digest(a, b)   # 常数时间比较

6.2 密钥派生 KDF

从密码派生密钥不能直接用哈希,要用专门的慢 KDF:

  • PBKDF2:迭代哈希,标准但抗 GPU 能力弱。
  • scrypt:内存硬,抗 ASIC。
  • Argon2:现代首选,可调内存/时间/并行度,密码哈希竞赛冠军。
  • HKDF:从已有高熵密钥材料扩展出多把子密钥(TLS 用)。

7. 数字签名与证书

7.1 签名流程

签名: sig = Sign(私钥, H(消息))
验证: Verify(公钥, H(消息), sig) == true

先哈希再签名是标准做法:既压缩了数据量,又固定了签名长度。

7.2 证书链

数字签名解决了「公钥是谁的」问题:证书 = 权威机构(CA)用自己私钥对「主体公钥 + 身份信息」的签名。

根 CA(自签,预置在系统/浏览器)
   └── 中间 CA(由根 CA 签发)
          └── 服务器证书(由中间 CA 签发,含域名与公钥)

验证时沿链逐级验签直到信任锚。中间 CA 的存在让根私钥可以离线保存,降低泄露风险。

openssl s_client -connect example.com:443 -showcerts   # 查看证书链
openssl x509 -in cert.pem -text -noout                 # 解析证书

8. TLS 握手与工程实践

8.1 TLS 1.3 握手流程

Client                                Server
  │── ClientHello (支持的套件/随机数) ──▶│
  │◀── ServerHello (选定套件/随机数) ────│
  │◀── 证书 + CertificateVerify ────────│   (服务器认证)
  │── Finished ────────────────────────▶│
  │◀──────── 应用数据(对称加密)────────│

TLS 1.3 把握手压缩到 1-RTT(会话恢复可 0-RTT),并强制前向保密(临时 ECDHE 密钥),废弃了 RSA 密钥传输、CBC、RC4 等不安全选项。

8.2 前向保密

使用**临时密钥交换(DHE/ECDHE)**后,即使服务器长期私钥泄露,也无法解密历史会话——因为每次会话的对称密钥是临时协商的、用完即弃。

8.3 工程清单

  • 启用 TLS 1.2/1.3,禁用 SSLv3、TLS 1.0/1.1。
  • 优先 ECDHE + AES-GCM 或 ChaCha20-Poly1305。
  • 启用 HSTS、OCSP Stapling,配置证书自动续期(如 ACME)。
  • 密钥材料放 KMS/HSM,不落配置文件、不进 Git。

9. 常见陷阱

  • 自己发明加密算法:几乎必然出错,用成熟库与标准算法。
  • ECB 模式:泄露明文模式,明文一图流。
  • nonce/IV 重用:CTR、GCM 下重用 nonce 会直接泄露明文异或结果,是灾难性错误。
  • 只加密不认证:缺少 MAC 会遭篡改与 padding oracle 攻击,应使用 AEAD。
  • 用 MD5/SHA-1 做安全用途:均已碰撞,仅可用于非安全校验。
  • 明文存密码或快速哈希:必须加盐 + 慢 KDF(Argon2/bcrypt)。
  • 时序侧信道:密钥、MAC、Token 比较必须常数时间。
  • 随机数用 random:安全场景必须用密码学安全随机源(secrets、/dev/urandom)。

参考文章

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「计算机基础」更多文章

  1. 34. 编程范式与类型系统
  2. 33. 分布式系统基础
  3. 31. 虚拟内存与分页