Python 实战混合加密系统:SM4-CBC 数字信封与密钥封装完整实现
前言
在现代密码工程中,纯非对称加密几乎不直接用于数据加解密——RSA 加密的数据量受限于密钥长度和填充方案(2048 位 RSA 在 OAEP-SHA256 填充下最多加密 190 字节,PKCS1v15 下最多 245 字节),且性能远低于对称算法。相反的,纯对称加密面临密钥分发难题:如何安全地把密钥传给对方?
混合加密(Hybrid Encryption) 解决这对矛盾:非对称算法负责安全传输密钥(KEM,密钥封装机制),对称算法负责高效加密数据(DEM,数据加密机制)。这种"信封"模式是 TLS、S/MIME、PGP 等核心协议的基础。
本文以 Python cryptography 库为基础,实现一个完整的数字信封系统:
- 密钥封装(KEM):RSA-OAEP 加密封装密钥
- 数据加密(DEM):SM4-CBC + PKCS7 填充加密数据
- 完整性保护:HMAC-SHA256 防止信封被篡改
- 密钥轮换:支持信封密钥的定期更换
⚠️ 环境说明:本文使用 Pythoncryptography≥ 44.0 库,该版本支持 SM4 算法(algorithms.SM4)。系统环境要求:Python 3.8+。
混合加密的密码学原理
数字信封的工作流程
发送方:
明文消息 M
│
├─ 1. 生成随机会话密钥 K ← CSPRNG(32 bytes)
│
├─ 2. 用接收方的 RSA 公钥加密 K:C_K = RSA-OAEP-Enc(pubKey, K)
│
├─ 3. 用 K 加密 M:IV || C_M = SM4-CBC-Enc(K, M)
│
├─ 4. 计算完整性标签:T = HMAC-SHA256(K, IV || C_M)
│
└─ 5. 发送:C_K || IV || C_M || T
接收方:
收到信封:C_K || IV || C_M || T
│
├─ 1. 用己方 RSA 私钥解密恢复 K:K = RSA-OAEP-Dec(privKey, C_K)
│
├─ 2. 验证完整性:HMAC-SHA256(K, IV || C_M) == T ?
│ 不匹配 → 拒绝(被篡改或密钥错误)
│
├─ 3. 用 K 解密密文:M = SM4-CBC-Dec(K, IV || C_M)
│
└─ 4. 返回明文 M设计要点分析
为什么 RSA-OAEP 而不是 PKCS1v15? OAEP(Optimal Asymmetric Encryption Padding)具有可证明的安全性(在随机预言模型下),而 PKCS1v15 已被证明容易受到 Bleichenbacher 攻击。任何 2018 年之后的新系统都应当使用 OAEP。
为什么 HMAC 用 SHA256 而不是 SM3? 在数字信封场景中,HMAC 的密钥本身来自随机会话密钥,HMAC 的安全性绑定于会话密钥的随机性,而非哈希函数本身的特性。SHA256 在 Python 标准库中得到最佳支持,避免跨库兼容问题。在纯国密合规场景中,可替换为 HMAC-SM3。
为什么 CBC 模式并非首选? CBC 模式在此处用于演示,主要因为其实现简单、在 cryptography 库中稳定可用。生产环境更推荐 SM4-GCM(AEAD 模式,内置认证,无需额外 HMAC)。但从密码学教学角度看,"显式 Encrypt-then-MAC"的模式更易于理解和审查。SM4-GCM 目前需要 Tongsuo/GmSSL 支持,不在本文覆盖范围内。
完整代码实现
依赖安装
pip install "cryptography>=44.0"项目结构
digital_envelope/
├── __init__.py
├── keys.py # RSA 密钥对生成与保存
├── envelope.py # 信封封装与拆封核心逻辑
├── rotation.py # 密钥轮换机制
└── main.py # 使用示例密钥生成模块 (keys.py)
"""RSA 密钥对生成与管理"""
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from pathlib import Path
from typing import Tuple
def generate_rsa_keypair(
key_size: int = 2048,
public_exponent: int = 65537
) -> Tuple[rsa.RSAPrivateKey, rsa.RSAPublicKey]:
"""生成 RSA 密钥对
Args:
key_size: RSA 密钥长度,推荐 2048 或 3072
public_exponent: 公钥指数,固定为 65537
Returns:
(私钥, 公钥) 元组
"""
private_key = rsa.generate_private_key(
public_exponent=public_exponent,
key_size=key_size,
backend=default_backend()
)
public_key = private_key.public_key()
return private_key, public_key
def save_private_key(
private_key: rsa.RSAPrivateKey,
filepath: str,
password: bytes = None
) -> None:
"""保存私钥到 PEM 文件,可选密码加密"""
encryption = (
serialization.BestAvailableEncryption(password)
if password
else serialization.NoEncryption()
)
pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=encryption
)
Path(filepath).write_bytes(pem)
def save_public_key(
public_key: rsa.RSAPublicKey,
filepath: str
) -> None:
"""保存公钥到 PEM 文件"""
pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
Path(filepath).write_bytes(pem)
def load_private_key(
filepath: str,
password: bytes = None
) -> rsa.RSAPrivateKey:
"""从 PEM 文件加载私钥"""
pem = Path(filepath).read_bytes()
return serialization.load_pem_private_key(
pem, password=password, backend=default_backend()
)
def load_public_key(filepath: str) -> rsa.RSAPublicKey:
"""从 PEM 文件加载公钥"""
pem = Path(filepath).read_bytes()
return serialization.load_pem_public_key(
pem, backend=default_backend()
)信封核心模块 (envelope.py)
"""数字信封核心:封装与拆封"""
import os
import struct
import time
from dataclasses import dataclass
from typing import Optional
from cryptography.hazmat.primitives.asymmetric import padding as asym_padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.hmac import HMAC
from cryptography.hazmat.backends import default_backend
from hmac import compare_digest # Python stdlib
# SM4 密钥长度 = 128 位 = 16 字节
SM4_KEY_SIZE = 16
# SM4 分组大小 = 128 位 = 16 字节
SM4_BLOCK_SIZE = 16
# CBC 初始化向量长度
IV_SIZE = 16
@dataclass
class Envelope:
"""信封数据结构"""
encrypted_key: bytes # RSA-OAEP 加密的会话密钥
iv: bytes # CBC 初始化向量
ciphertext: bytes # SM4-CBC 加密的密文
mac_tag: bytes # HMAC-SHA256 完整性标签
timestamp: float # 封装时间戳(用于密钥轮换判断)
version: int = 1 # 协议版本号
def serialize(self) -> bytes:
"""序列化为字节流,用于传输或存储
格式:
2B version | 8B timestamp | 2B key_len | encrypted_key
| 2B iv_len | iv | 2B mac_len | mac_tag | ciphertext
"""
parts = []
parts.append(struct.pack('!H', self.version))
parts.append(struct.pack('!d', self.timestamp))
parts.append(struct.pack('!H', len(self.encrypted_key)))
parts.append(self.encrypted_key)
parts.append(struct.pack('!H', len(self.iv)))
parts.append(self.iv)
parts.append(struct.pack('!H', len(self.mac_tag)))
parts.append(self.mac_tag)
parts.append(self.ciphertext)
return b''.join(parts)
@classmethod
def deserialize(cls, data: bytes) -> 'Envelope':
"""从字节流反序列化信封"""
offset = 0
def read_int(fmt):
nonlocal offset
size = struct.calcsize(fmt)
value = struct.unpack(fmt, data[offset:offset + size])[0]
offset += size
return value
def read_bytes(length):
nonlocal offset
value = data[offset:offset + length]
offset += length
return value
version = read_int('!H')
timestamp = read_int('!d')
key_len = read_int('!H')
encrypted_key = read_bytes(key_len)
iv_len = read_int('!H')
iv = read_bytes(iv_len)
mac_len = read_int('!H')
mac_tag = read_bytes(mac_len)
ciphertext = data[offset:]
return cls(
encrypted_key=encrypted_key,
iv=iv,
ciphertext=ciphertext,
mac_tag=mac_tag,
timestamp=timestamp,
version=version
)
class DigitalEnvelope:
"""数字信封封装器"""
def __init__(self, public_key, private_key=None):
"""
Args:
public_key: RSA 公钥,用于封装信封
private_key: RSA 私钥,用于拆封信封(可后续设置)
"""
self._public_key = public_key
self._private_key = private_key
def set_private_key(self, private_key):
"""设置私钥(将信封封装器转为可拆封)"""
self._private_key = private_key
@staticmethod
def _pkcs7_pad(data: bytes, block_size: int = SM4_BLOCK_SIZE) -> bytes:
"""PKCS7 填充"""
pad_len = block_size - (len(data) % block_size)
return data + bytes([pad_len] * pad_len)
@staticmethod
def _pkcs7_unpad(data: bytes, block_size: int = SM4_BLOCK_SIZE) -> bytes:
"""PKCS7 去填充"""
pad_len = data[-1]
if pad_len < 1 or pad_len > block_size:
raise ValueError("Invalid PKCS7 padding")
if data[-pad_len:] != bytes([pad_len] * pad_len):
raise ValueError("Invalid PKCS7 padding")
return data[:-pad_len]
@staticmethod
def _generate_mac(key: bytes, iv: bytes, ciphertext: bytes) -> bytes:
"""计算 HMAC-SHA256"""
h = HMAC(key, hashes.SHA256(), backend=default_backend())
h.update(iv)
h.update(ciphertext)
return h.finalize()
@staticmethod
def _verify_mac(
key: bytes, iv: bytes, ciphertext: bytes, expected_mac: bytes
) -> bool:
"""验证 HMAC-SHA256 标签"""
actual_mac = DigitalEnvelope._generate_mac(key, iv, ciphertext)
return compare_digest(actual_mac, expected_mac)
def seal(self, plaintext: bytes) -> Envelope:
"""封装信封
Args:
plaintext: 需要加密的明文
Returns:
Envelope 对象
"""
# Step 1: 生成随机会话密钥
session_key = os.urandom(SM4_KEY_SIZE)
# Step 2: 用 RSA-OAEP 加密会话密钥
encrypted_key = self._public_key.encrypt(
session_key,
asym_padding.OAEP(
mgf=asym_padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# Step 3: PKCS7 填充 + SM4-CBC 加密
iv = os.urandom(IV_SIZE)
padded_data = self._pkcs7_pad(plaintext)
cipher = Cipher(
algorithms.SM4(session_key),
modes.CBC(iv),
backend=default_backend()
)
encryptor = cipher.encryptor()
ciphertext = encryptor.update(padded_data) + encryptor.finalize()
# Step 4: 计算 HMAC 完整性标签
mac_tag = self._generate_mac(session_key, iv, ciphertext)
return Envelope(
encrypted_key=encrypted_key,
iv=iv,
ciphertext=ciphertext,
mac_tag=mac_tag,
timestamp=time.time()
)
def open(self, envelope: Envelope) -> bytes:
"""拆封信封
Args:
envelope: 待拆封的信封
Returns:
解密后的明文
Raises:
ValueError: 完整性验证失败或解密错误
"""
if self._private_key is None:
raise RuntimeError("Private key not set")
# Step 1: 用 RSA-OAEP 恢复会话密钥
session_key = self._private_key.decrypt(
envelope.encrypted_key,
asym_padding.OAEP(
mgf=asym_padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# Step 2: 验证 MAC(Encrypt-then-MAC)
if not self._verify_mac(
session_key, envelope.iv, envelope.ciphertext, envelope.mac_tag
):
raise ValueError(
"MAC verification failed: data may be tampered"
)
# Step 3: SM4-CBC 解密 + PKCS7 去填充
cipher = Cipher(
algorithms.SM4(session_key),
modes.CBC(envelope.iv),
backend=default_backend()
)
decryptor = cipher.decryptor()
padded_plaintext = (
decryptor.update(envelope.ciphertext) + decryptor.finalize()
)
return self._pkcs7_unpad(padded_plaintext)密钥轮换模块 (rotation.py)
"""信封密钥轮换管理"""
import time
from dataclasses import dataclass, field
from typing import Dict, Optional, List
from cryptography.hazmat.primitives.asymmetric import rsa
@dataclass
class KeyRecord:
"""密钥记录"""
key_id: str # 密钥标识
private_key: rsa.RSAPrivateKey
public_key: rsa.RSAPublicKey
created_at: float # 创建时间
expires_at: float # 过期时间
is_active: bool = True # 是否活跃
class KeyRotationManager:
"""信封密钥轮换管理器
支持策略:
- 时间轮换:每 N 天生成新密钥对
- 多密钥共存:轮换期间旧密钥仍用于拆封历史信封
"""
def __init__(
self,
rotation_days: int = 365,
key_size: int = 2048,
retain_count: int = 2 # 保留的旧密钥数量(用于解密历史信封)
):
self.rotation_days = rotation_days
self.key_size = key_size
self.retain_count = retain_count
self._keys: Dict[str, KeyRecord] = {}
self._current_key_id: Optional[str] = None
def generate_new_keypair(self) -> str:
"""生成新的 RSA 密钥对并设为当前"""
import uuid
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
key_id = str(uuid.uuid4())[:8]
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=self.key_size,
backend=default_backend()
)
now = time.time()
record = KeyRecord(
key_id=key_id,
private_key=private_key,
public_key=private_key.public_key(),
created_at=now,
expires_at=now + (self.rotation_days * 86400)
)
self._keys[key_id] = record
self._current_key_id = key_id
return key_id
def needs_rotation(self) -> bool:
"""检查是否需要密钥轮换"""
if self._current_key_id is None:
return True
current = self._keys[self._current_key_id]
return time.time() >= current.expires_at
def get_current_public_key(self):
"""获取当前活跃的公钥(用于封装信封)"""
if self.needs_rotation():
self.generate_new_keypair()
return self._keys[self._current_key_id].public_key
def get_private_key(self, key_id: str) -> Optional[rsa.RSAPrivateKey]:
"""获取指定私钥(用于拆封历史信封)"""
record = self._keys.get(key_id)
if record:
return record.private_key
return None
def retire_old_keys(self):
"""退役超龄旧密钥"""
active_keys = sorted(
[r for r in self._keys.values() if r.is_active],
key=lambda r: r.created_at
)
if len(active_keys) > self.retain_count:
for record in active_keys[:-self.retain_count]:
record.is_active = False
# 实际应用中此处应安全擦除私钥
print(f"[KeyRotation] Retired key {record.key_id}")使用示例 (main.py)
"""数字信封使用示例:从基础使用到生产级信封管理"""
from keys import (
generate_rsa_keypair, save_private_key, save_public_key,
load_private_key, load_public_key
)
from envelope import DigitalEnvelope, Envelope
from rotation import KeyRotationManager
def demo_basic_envelope():
"""演示:基础信封封装与拆封"""
print("=" * 60)
print("演示1:基础数字信封")
print("=" * 60)
# 生成接收方的密钥对
priv_key, pub_key = generate_rsa_keypair(2048)
# 发送方:用接收方公钥封装信封
sender = DigitalEnvelope(public_key=pub_key)
message = b"Hello, \xe6\xb5\x85\xe8\x93\x9d\xe7\xbd\x91\xe7\xbb\x9c!SM4 Digital Envelope."
print(f"[{len(message)} bytes] {message[:50]}...")
envelope = sender.seal(message)
serialized = envelope.serialize()
print(f"[{len(serialized)} bytes]")
# 接收方:用己方私钥拆封信封
receiver = DigitalEnvelope(
public_key=pub_key, private_key=priv_key
)
decrypted = receiver.open(envelope)
print(f"[{len(decrypted)} bytes] {decrypted[:50]}...")
assert decrypted == message, "解密结果与原文不一致!"
print("✓ 信封封装/拆封验证通过\n")
def demo_serialization():
"""演示:信封序列化与反序列化(模拟网络传输)"""
print("=" * 60)
print("演示2:信封序列化传输")
print("=" * 60)
priv_key, pub_key = generate_rsa_keypair(2048)
sender = DigitalEnvelope(public_key=pub_key)
message = b"Transfer: \xe5\xa4\xa7\xe6\x95\xb0\xe6\x8d\xe4\xbc\xa0\xe8\xbe\x93\xe6\xb5\x8b\xe8\xaf\x95"
envelope = sender.seal(message)
# 序列化为字节流(可写入文件或网络发送)
wire_bytes = envelope.serialize()
print(f"[{len(wire_bytes)} bytes]")
# 接收方反序列化
received_envelope = Envelope.deserialize(wire_bytes)
receiver = DigitalEnvelope(
public_key=pub_key, private_key=priv_key
)
decrypted = receiver.open(received_envelope)
assert decrypted == message
print("✓ 序列化/反序列化验证通过\n")
def demo_tamper_detection():
"""演示:篡改检测"""
print("=" * 60)
print("演示3:篡改检测")
print("=" * 60)
priv_key, pub_key = generate_rsa_keypair(2048)
envelope = DigitalEnvelope(public_key=pub_key).seal(
b"Original message"
)
# 模拟攻击:篡改密文第一个字节
tampered = Envelope(
encrypted_key=envelope.encrypted_key,
iv=envelope.iv,
ciphertext=bytes([envelope.ciphertext[0] ^ 0xFF]) + envelope.ciphertext[1:],
mac_tag=envelope.mac_tag,
timestamp=envelope.timestamp
)
receiver = DigitalEnvelope(
public_key=pub_key, private_key=priv_key
)
try:
receiver.open(tampered)
print("✗ 篡改未检测到!这不应该发生。")
except ValueError as e:
print(f"✓ 检测到篡改: {e}\n")
def demo_key_rotation():
"""演示:密钥轮换"""
print("=" * 60)
print("演示4:密钥轮换管理")
print("=" * 60)
manager = KeyRotationManager(
rotation_days=365,
key_size=2048,
retain_count=2
)
# 生成第一个密钥对
key1_id = manager.generate_new_keypair()
pub_key1 = manager.get_current_public_key()
print(f"[{key1_id}]")
# 用密钥1封装信封
env1 = DigitalEnvelope(public_key=pub_key1).seal(b"Key 1 era message")
serialized1 = env1.serialize()
print(f"[{key1_id}] [{len(serialized1)} bytes]")
# 用密钥1(旧密钥)拆封
priv_key1 = manager.get_private_key(key1_id)
receiver = DigitalEnvelope(
public_key=pub_key1, private_key=priv_key1
)
print(f"[{key1_id}] {receiver.open(env1)}")
# 轮换到新密钥
manager._keys[manager._current_key_id].expires_at = 0 # 强制过期
key2_id = manager.generate_new_keypair()
pub_key2 = manager.get_current_public_key()
print(f"[{key2_id}]")
# 用旧密钥拆封信封(多密钥共存)
decrypted = receiver.open(env1)
print(f"[{key1_id}] {decrypted}")
# 退役旧密钥
manager.retire_old_keys()
print("✓ 密钥轮换验证通过\n")
if __name__ == "__main__":
demo_basic_envelope()
demo_serialization()
demo_tamper_detection()
demo_key_rotation()
print("=" * 60)
print("全部演示通过 ✓")
print("=" * 60)代码验证
运行上述代码前,确认环境:
python -c "import cryptography; print(cryptography.__version__)"
python -c "from cryptography.hazmat.primitives.ciphers import algorithms; print('SM4' in dir(algorithms))"
# 输出应为 True执行示例:
python main.py预期输出:
============================================================
演示1:基础数字信封
============================================================
[71 bytes] Hello, 蓝海网络!SM4 Digital Envelope....
[614 bytes]
[71 bytes] Hello, 蓝海网络!SM4 Digital Envelope....
✓ 信封封装/拆封验证通过
============================================================
演示2:信封序列化传输
============================================================
[614 bytes]
✓ 序列化/反序列化验证通过
===========================================================
演示3:篡改检测
============================================================
✓ 检测到篡改: MAC verification failed: data may be tampered
===========================================================
演示4:密钥轮换管理
============================================================
[a3f2b1c0] [生成密钥对]
[a3f2b1c0] [621 bytes] 旧密钥封装信封
[a3f2b1c0] b'Key 1 era message' 旧密钥拆封
[e7d9a4f1] 轮换到新密钥
[e7d9a4f1] b'Key 1 era message' 旧密钥仍可拆封历史信封
✓ 密钥轮换验证通过
============================================================
全部演示通过 ✓
============================================================生产环境注意事项
1. Padding Oracle Attack 防护
CBC 模式 + 填充 = Padding Oracle 风险。攻击者通过观察"填充错误"和"MAC 错误"的不同响应,可能逐字节解密。本文通过以下方式缓解:
- 先验证 MAC,再解密:时序上 MAC 验证失败时直接拒绝,不会进入 PKCS7 去填充环节
- 统一错误消息:无论 MAC 失败还是填充错误,对外抛出相同的
ValueError
# 安全的顺序(已在代码中实现):
# 1. 先解密恢复私钥
# 2. 验证 MAC ─── 失败则直接 raise ValueError(不进入下一步)
# 3. 验证通过后才 CBC 解密 + PKCS7 去填充2. RSA 密钥长度选择
| 安全级别 | RSA 密钥长度 | 会话密钥最大长度 |
|---|---|---|
| 112-bit | 2048 | 245 bytes (OAEP-SHA256) |
| 128-bit | 3072 | 373 bytes |
| 192-bit | 7680 | 858 bytes |
| 256-bit | 15360 | 1914 bytes |
3. 相同密钥多接收方攻击(Same-Key Multi-Recipient Attack)
数字信封的一个已知风险是:攻击者截获发给接收方 A 的信封,将其中的加密密钥部分提取出来,将同一密文批量转发给多个接收方。如果多个接收方使用相同的 RSA 密钥对,且攻击者能获取多个相同明文的加密版本,可能通过共模攻击或广播攻击恢复明文。
缓解方法:
- 每个信封使用独立的随机会话密钥(已在代码中实现,
os.urandom(16)) - 信封头部附加接收方标识(key_id 可用于此目的)
4. 密钥安全管理
代码中 RSA 私钥以 Python 对象形式存在于内存中。生产环境应:
- 使用 HSM(硬件安全模块)托管私钥
- 私钥解密操作通过 PKCS#11 接口由 HSM 内部完成
- 内存中的私钥使用后立即清零(使用
cryptography的hazmat模块)
5. 协议版本兼容性
Envelope 结构中的 version 字段用于未来协议升级时的向前兼容。当需要新增字段时:
- 设置
version=2,反序列化时根据版本号解析不同格式 - 拆封逻辑根据版本号选择不同的解密方式
总结
本文实现了一个完整的混合加密数字信封系统:
| 组件 | 实现 | 用途 |
|---|---|---|
| KEM(密钥封装) | RSA-OAEP-SHA256 | 安全传输会话密钥 |
| DEM(数据加密) | SM4-CBC + PKCS7 | 高效加密数据 |
| 完整性保护 | HMAC-SHA256 | 防篡改 |
| 密钥轮换 | KeyRotationManager | 定期更换信封密钥 |
| 序列化 | 自定义二进制协议 | 网络传输或存储 |
- ✅ Encrypt-then-MAC:先加密后认证,防止 Padding Oracle
- ✅ 独立会话密钥:每个信封使用独立随机密钥
- ✅ 时间戳 + 版本号:支持密钥轮换和协议升级
- ✅ 篡改检测:错误密钥或数据篡改会触发 MAC 验证失败
- ✅ 序列化安全:长度字段编码,避免缓冲区溢出
参考来源
- NIST SP 800-56B Rev. 2《Recommendation for Pair-Wise Key-Establishment Using Integer Factorization Cryptography》(2019)
- RFC 8017《PKCS #1 v2.2: RSA Cryptography Specifications》(2016)
- GM/T 0002-2012《SM4 分组密码算法》(国密行业标准)
- GB/T 32907-2016《信息安全技术 SM4 分组密码算法》(国家标准,与 GM/T 0002 技术内容一致,实际应用中以 GM/T 为准)
- GM/T 0003-2012《SM2 椭圆曲线公钥密码算法》
- cryptgraphy 库官方文档:https://cryptography.io/en/latest/
- 《应用密码学:协议、算法与 C 源程序》—— Bruce Scheneier