开篇:密码学——信息安全的基石
密码学是信息安全的数学基础。从古代的凯撒密码到现代的 AES-256、从 HTTP 到 HTTPS 的普及、从 RSA-2048 到后量子密码学——密码学的发展史就是人类保护信息不被窃取和篡改的演进史。
本章将系统介绍现代密码学的核心概念与实战技术:对称加密(AES-GCM、ChaCha20-Poly1305)、非对称加密(RSA/ECC)、密钥派生(Argon2)、TLS 1.3 协议、密钥管理架构,以及正在到来的后量子密码学时代。
一、密码学基础概念
1.1 核心分类
| 类型 | 算法 | 用途 | 特点 |
|---|---|---|---|
| 对称加密 | AES, ChaCha20 | 大量数据加密 | 速度快,需安全共享密钥 |
| 非对称加密 | RSA, ECC, Ed25519 | 密钥交换、数字签名 | 速度较慢,无需共享密钥 |
| 哈希函数 | SHA-256, SHA-3, Blake3 | 数据完整性、密码存储 | 单向、固定长度输出 |
| 消息认证码 | HMAC | 完整性 + 认证 | 需要密钥 |
| 认证加密 | AES-GCM, ChaCha20-Poly1305 | 加密 + 完整性 | AEAD 模式,推荐 |
1.2 关键原则
- 柯克霍夫原则:密码系统的安全性应仅依赖于密钥的保密,而非算法的保密
- 绝不自行实现加密算法:始终使用经过审计的标准库
- 认证加密(AEAD)优先:同时保证机密性和完整性
- 前向保密(Forward Secrecy):即使长期私钥泄露,历史会话也不受影响
一句话总结:密码学的黄金法则是"不要自己造轮子"——使用经过时间检验的标准算法和经过审计的实现库。
二、对称加密实战
2.1 AES-256-GCM
// Go 实现 AES-256-GCM
package crypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
)
// Encrypt 使用 AES-256-GCM 加密
type AESGCM struct {
key []byte // 32 bytes for AES-256
}
func NewAESGCM(key []byte) (*AESGCM, error) {
if len(key) != 32 {
return nil, fmt.Errorf("key must be 32 bytes for AES-256")
}
return &AESGCM{key: key}, nil
}
func (a *AESGCM) Encrypt(plaintext []byte) (string, error) {
block, err := aes.NewCipher(a.key)
if err != nil {
return "", err
}
// GCM 模式
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
// 随机生成 nonce(每次加密必须唯一)
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
// Seal 自动附加认证标签
ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
func (a *AESGCM) Decrypt(ciphertextBase64 string) ([]byte, error) {
ciphertext, err := base64.StdEncoding.DecodeString(ciphertextBase64)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(a.key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return nil, fmt.Errorf("ciphertext too short")
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
return gcm.Open(nil, nonce, ciphertext, nil)
}
2.2 ChaCha20-Poly1305
# Python 实现 ChaCha20-Poly1305
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
import os
import base64
def encrypt_chacha20(key: bytes, plaintext: bytes, associated_data: bytes = None) -> str:
"""ChaCha20-Poly1305 加密"""
chacha = ChaCha20Poly1305(key)
nonce = os.urandom(12) # 96-bit nonce
ciphertext = chacha.encrypt(nonce, plaintext, associated_data)
# 拼接 nonce + ciphertext
result = nonce + ciphertext
return base64.b64encode(result).decode()
def decrypt_chacha20(key: bytes, ciphertext_b64: str, associated_data: bytes = None) -> bytes:
"""ChaCha20-Poly1305 解密"""
data = base64.b64decode(ciphertext_b64)
nonce = data[:12]
ciphertext = data[12:]
chacha = ChaCha20Poly1305(key)
return chacha.decrypt(nonce, ciphertext, associated_data)
# 使用
key = ChaCha20Poly1305.generate_key() # 32 bytes
encrypted = encrypt_chacha20(key, b"Hello, World!", b"header-data")
print(decrypt_chacha20(key, encrypted, b"header-data"))
一句话总结:AES-256-GCM 和 ChaCha20-Poly1305 是目前最推荐的两种 AEAD 算法——AES 硬加速友好(Intel AES-NI),ChaCha20 在纯软件实现上更有优势。
三、密钥派生(Password Hashing)
3.1 Argon2(获奖算法)
from argon2 import PasswordHasher
# Argon2id(当前推荐版本,抵抗侧信道攻击和 GPU 破解)
ph = PasswordHasher(
time_cost=3, # 迭代次数
memory_cost=65536, # 64MB 内存
parallelism=4, # 并行度
hash_len=32,
salt_len=16,
)
# 哈希密码
hash = ph.hash("user_password")
# $argon2id$v=19$m=65536,t=3,p=4$c2FsdA...$hash...
# 验证密码
try:
ph.verify(hash, "user_password")
print("验证成功")
# 检查是否需要重新哈希(参数升级)
if ph.check_needs_rehash(hash):
new_hash = ph.hash("user_password")
except:
print("验证失败")
3.2 密钥派生函数(KDF)
// HKDF:从 master key 派生子密钥
import (
"crypto/sha256"
"golang.org/x/crypto/hkdf"
"io"
)
func deriveKeys(masterKey []byte, salt []byte) (encKey []byte, macKey []byte, err error) {
hkdfReader := hkdf.New(sha256.New, masterKey, salt, []byte("my-app-key-derivation"))
encKey = make([]byte, 32)
if _, err = io.ReadFull(hkdfReader, encKey); err != nil {
return nil, nil, err
}
macKey = make([]byte, 32)
if _, err = io.ReadFull(hkdfReader, macKey); err != nil {
return nil, nil, err
}
return encKey, macKey, nil
}
一句话总结:Argon2id 是目前密码哈希的最佳实践,而 HKDF 则是从主密钥安全派生多个子密钥的标准方案。
四、非对称加密与数字签名
4.1 Ed25519 签名(推荐替代 RSA)
package main
import (
"crypto/ed25519"
"encoding/base64"
"fmt"
)
func main() {
// 生成密钥对
pubKey, privKey, err := ed25519.GenerateKey(nil)
if err != nil {
panic(err)
}
message := []byte("Hello, Ed25519!")
// 签名
signature := ed25519.Sign(privKey, message)
fmt.Printf("Signature: %s\n", base64.StdEncoding.EncodeToString(signature))
// 验证
valid := ed25519.Verify(pubKey, message, signature)
fmt.Printf("Valid: %v\n", valid)
}
4.2 ECDH 密钥交换
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
from cryptography.hazmat.primitives import serialization
# Alice
alice_private = X25519PrivateKey.generate()
alice_public = alice_private.public_key()
# Bob
bob_private = X25519PrivateKey.generate()
bob_public = bob_private.public_key()
# 交换公钥后计算共享密钥
alice_shared = alice_private.exchange(bob_public)
bob_shared = bob_private.exchange(alice_public)
# alice_shared == bob_shared ✅
print(f"Shared secret match: {alice_shared == bob_shared}")
一句话总结:Ed25519 和 X25519 是 Daniel Bernstein 设计的现代椭圆曲线算法,比 RSA 更快、密钥更短、安全性参数等价于 RSA-3072。
五、TLS 1.3 深度解析
5.1 TLS 1.3 握手流程
ClientHello (包含 KeyShare) ───────────────────────►
(服务器选择参数)
◄────────────────────── ServerHello + EncryptedExtensions
+ {Certificate} + {Finished}
{Finished} + Application Data ─────────────────────►
{} 表示加密的消息
TLS 1.3 特点:
- 1-RTT 握手(相比 TLS 1.2 的 2-RTT)
- 0-RTT 会话恢复(权衡安全性与性能)
- 强制前向保密(所有密钥交换算法)
- 移除不安全的密码套件
5.2 TLS 配置最佳实践
# Nginx TLS 1.3 配置
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/ssl/certs/example.crt;
ssl_certificate_key /etc/ssl/private/example.key;
# TLS 1.2 + 1.3(逐步淘汰 TLS 1.2)
ssl_protocols TLSv1.3;
# ssl_protocols TLSv1.2 TLSv1.3;
# 密码套件(TLS 1.3 固定套件,无需配置)
# TLS 1.2 套件配置
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
# Session tickets
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
}
一句话总结:TLS 1.3 将握手减少到 1-RTT、移除了大量不安全选项、强制前向保密——是现代 Web 安全通信的最低标准。
六、密钥管理
6.1 Envelope Encryption(信封加密)
明文数据
↓
[随机生成 DEK(数据加密密钥)] ──► AES-256-GCM 加密数据
│ ↓
│ 加密后的数据
↓
[用 KEK(密钥加密密钥)加密 DEK] ──► 加密后的 DEK
↓
存储:{加密数据, 加密 DEK}
# AWS KMS Envelope Encryption 示例
import boto3
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
class EnvelopeEncryption:
def __init__(self, key_id: str):
self.kms = boto3.client('kms')
self.key_id = key_id
def encrypt(self, plaintext: bytes) -> dict:
# 1. 生成随机 DEK
dek = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(dek)
# 2. 用 DEK 加密数据
nonce = os.urandom(12)
ciphertext = aesgcm.encrypt(nonce, plaintext, None)
# 3. 用 KMS 加密 DEK
response = self.kms.encrypt(KeyId=self.key_id, Plaintext=dek)
encrypted_dek = response['CiphertextBlob']
return {
'ciphertext': ciphertext,
'nonce': nonce,
'encrypted_dek': encrypted_dek,
}
def decrypt(self, data: dict) -> bytes:
# 1. 用 KMS 解密 DEK
response = self.kms.decrypt(CiphertextBlob=data['encrypted_dek'])
dek = response['Plaintext']
# 2. 用 DEK 解密数据
aesgcm = AESGCM(dek)
return aesgcm.decrypt(data['nonce'], data['ciphertext'], None)
6.2 HSM 与密钥分片
# Shamir's Secret Sharing:将密钥分成 n 份,任意 k 份可恢复
from secretsharing import SecretSharer
secret = "my-master-key-1234567890abcdef"
# 分成 5 份,任意 3 份可恢复
shares = SecretSharer.split_secret(secret, 3, 5)
# shares = [share1, share2, share3, share4, share5]
# 分发到不同地理位置
# share1 -> 北京数据中心
# share2 -> 上海数据中心
# share3 -> 深圳数据中心
# share4 -> 香港 K Safe (离线)
# share5 -> 新加坡数据中心
# 恢复(只需要任意 3 份)
recovered = SecretSharer.recover_secret([shares[0], shares[2], shares[4]])
assert recovered == secret
一句话总结:密钥管理的最高境界是"数据加密密钥不落地"——通过 KMS/HSM 保护根密钥,通过信封加密派生数据密钥,通过分片实现高可用与灾难恢复。
七、后量子密码学
7.1 NIST PQC 标准化算法
| 算法类型 | 算法名称 | 用途 |
|---|---|---|
| KEM(密钥封装) | CRYSTALS-Kyber | 替代 ECDH 密钥交换 |
| 数字签名 | CRYSTALS-Dilithium | 替代 ECDSA/Ed25519 |
| 数字签名 | SPHINCS+ | 基于哈希的有状态/无状态签名 |
| 数字签名 | FALCON | NTRU 格签名 |
7.2 迁移策略
当前阶段(2024-2026):混合加密
┌─────────────────────────────────────┐
│ 传统算法(ECC/RSA)+ 后量子算法 │
│ 并存使用,任一被破解仍有保护 │
└─────────────────────────────────────┘
过渡阶段(2027-2030):逐步淘汰
┌─────────────────────────────────────┐
│ 后量子算法为主,传统算法为辅 │
│ 移除已知被量子计算机威胁的算法 │
└─────────────────────────────────────┘
目标阶段(2030+):纯后量子
┌─────────────────────────────────────┐
│ 仅使用后量子算法 │
│ 传统算法仅用于兼容旧系统 │
└─────────────────────────────────────┘
一句话总结:后量子密码学不是"如果"而是"何时"的问题——谷歌和 Cloudflare 已经在生产环境测试混合 PQ 加密,迁移准备应从现在开始。
FAQ
Q1: AES-256-GCM 的 nonce 重复使用会怎样?
灾难性的。GCM 模式在 nonce 重复时会完全破坏机密性,攻击者可以恢复密钥流。解决方案:使用 96-bit 随机 nonce( birthday bound 约为 2^48 条消息,远超大多数应用需求),或使用递增计数器。
Q2: 为什么推荐用 Argon2id 而不是 bcrypt/scrypt?
Argon2 是 2015 年密码哈希竞赛的获胜算法:
- bcrypt:只使用 CPU,不抵抗 GPU
- scrypt:使用内存,但参数僵化
- Argon2id:同时使用时间和内存,抵抗侧信道攻击和 GPU
Q3: TLS 1.3 的 0-RTT 有什么安全风险?
0-RTT 允许客户端在握手完成前发送数据,但存在重放攻击风险。建议:0-RTT 仅用于幂等操作(如 GET 请求),绝不用于状态变更操作。
Q4: 量子计算机真的会破解现有加密吗?
- 对称加密(AES):Grover 算法将有效密钥长度减半,AES-256 仍安全
- 非对称加密(RSA/ECC):Shor 算法可在多项式时间内破解,需要迁移到后量子算法
- 哈希函数(SHA-256):Grover 算法影响有限,仍安全
Q5: 加密数据的密钥该如何备份?
- KMS 自动备份(AWS KMS/GCP Cloud KMS/Azure Key Vault)
- HSM 集群冗余
- Shamir 分片离线存储(银行金库式)
- 定期演练密钥恢复流程
Q6: libsodium 为什么被推荐?
libsodium 是 NaCl 的分支,提供:
- 简单的高层次 API(难以误用)
- 默认安全参数(无需手动选择)
- 现代算法(ChaCha20-Poly1305、Ed25519、X25519、Argon2)
- 跨平台支持
相关阅读
- https://plumephp.com/security-secret-management/ — 密钥与凭证管理
- https://plumephp.com/security-compliance-data-protection/ — 安全合规与数据保护
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。