对称加密(Symmetric Cryptography)是密码学中最基础、最核心的加密范式,其本质特征是加密和解密使用完全相同的密钥。这种"一把钥匙开一把锁"的对称性,使得对称加密在加解密速度上远超非对称加密,成为数据加密保密的工业标准。从亿级用户的 HTTPS 连接保护,到嵌入式设备的固件加密,对称加密无处不在。
对称加密的基本模型可以描述为:
明文 P + 密钥 K → 加密算法 E → 密文 C
密文 C + 密钥 K → 解密算法 D → 明文 P
核心约束条件:
| 特性 | 对称加密 | 非对称加密 |
|---|---|---|
| 密钥数量 | 1个共享密钥 | 公钥+私钥一对 |
| 加解密速度 | 快(软件可达 1-10 GB/s) | 慢(RSA-2048 约 0.1-1 MB/s) |
| 密钥长度 | 128-256 bit | 2048-4096 bit |
| 安全性基础 | 混淆+扩散(S-box、P盒) | 数学难题(大数分解、离散对数) |
| 主要用途 | 批量数据加密 | 密钥协商、数字签名 |
| 经典算法 | AES、ChaCha20 | RSA、ECC |
为了直观理解对称加密的"混淆"过程,考虑一个简化版的分组加密:4-bit 分组 + 一个简单的替换表。
假设明文为 1010(十进制 10),密钥为 0110(十进制 6):
| 步骤 | 操作 | 输入 | 输出 |
|---|---|---|---|
| 1 | 明文 XOR 密钥 | 1010 ⊕ 0110 |
1100 (12) |
| 2 | S盒替换(查表) | 1100 → 查预设S盒 |
0011 (3) |
| 3 | 行移位 | 按规则重排 | 0110 (6) |
| 4 | 第2轮密钥XOR | 0110 ⊕ 0110 |
0000 (0) |
这个过程使用了 AES 的思想——通过**替换(Substitution)和置换(Permutation)**的多次迭代来实现混淆。
对称加密按处理数据的方式可分为分组密码和流密码两大类。
分组密码将明文切分为固定长度的数据块,逐块加密。
| 算法 | 分组大小 | 密钥长度 | 轮数 | 设计年份 | 状态 |
|---|---|---|---|---|---|
| DES | 64 bit | 56 bit | 16 | 1977 | ❌ 已破解(56-bit密钥可在24小时内暴力破解) |
| 3DES | 64 bit | 112/168 bit | 48 | 1998 | ⚠️ 逐步弃用(NIST已于2023年正式弃用) |
| AES-128 | 128 bit | 128 bit | 10 | 2001 | ✅ 安全 |
| AES-192 | 128 bit | 192 bit | 12 | 2001 | ✅ 安全 |
| AES-256 | 128 bit | 256 bit | 14 | 2001 | ✅ 安全 |
| SM4 | 128 bit | 128 bit | 32 | 2006 | ✅ 国密标准 |
| Blowfish | 64 bit | 32-448 bit | 16 | 1993 | ⚠️ 64-bit分组易受生日攻击 |
| Twofish | 128 bit | 128-256 bit | 16 | 1998 | ✅ 安全但未广泛采用 |
AES(Advanced Encryption Standard)是当今使用最广泛的对称加密算法。它以字节为基本操作单元,处理 128-bit(16字节)的数据块。
AES-128 的10轮加密流程:
明文(128 bit = 16字节)
│
▼
┌─────────────────────────┐
│ AddRoundKey │ ← 初始轮:密钥 XOR
│ (与轮密钥异或) │
└─────────┬───────────────┘
│
▼ (重复9轮)
┌─────────────────────────┐
│ 1. SubBytes │ ← S盒替换(非线性)
│ 2. ShiftRows │ ← 行移位(扩散)
│ 3. MixColumns │ ← 列混合(扩散)
│ 4. AddRoundKey │ ← 轮密钥XOR
└─────────┬───────────────┘
│
▼ (第10轮,无 MixColumns)
┌─────────────────────────┐
│ 1. SubBytes │
│ 2. ShiftRows │
│ 3. AddRoundKey │
└─────────┬───────────────┘
│
▼
密文(128 bit = 16字节)
S盒替换示例(SubBytes):
AES 的 S 盒是一个 16×16 的查找表。假设输入字节为 0x53:
0x5 作为行索引,低4位 0x3 作为列索引0xED| 输入 | S盒行 | S盒列 | 输出 | 效果 |
|---|---|---|---|---|
| 0x53 | 5 | 3 | 0xED | 完全不可预测 |
| 0x00 | 0 | 0 | 0x63 | 非零映射(避免固定点) |
| 0xFF | F | F | 0x16 | 满射 |
S 盒的数学基础是 上的乘法逆元加仿射变换:,其中 是一个 8×8 的可逆仿射矩阵, 是一个常数向量。这种设计保证了 S 盒具有:
MixColumns 数值示例:
MixColumns 将一个 4 字节的列视为 上的多项式,乘以固定多项式 。
假设某一列的 4 个字节为 [0xd4, 0xbf, 0x5d, 0x30],计算第一个输出字节:
output[0] = (0x02 × 0xd4) ⊕ (0x03 × 0xbf) ⊕ (0x01 × 0x5d) ⊕ (0x01 × 0x30)
0x02 × 0xd4 = 0xb3 (在GF(2^8)上的乘法,小于0x80时不需约简)
0x03 × 0xbf = (0x02 × 0xbf) ⊕ 0xbf = 0x64 ⊕ 0xbf = 0xdb
0x01 × 0x5d = 0x5d
0x01 × 0x30 = 0x30
output[0] = 0xb3 ⊕ 0xdb ⊕ 0x5d ⊕ 0x30 = 0x05
这种在有限域上的算术运算提供了强大的扩散效果——改变明文的一个字节会影响整个密文块的多个字节。
AES-128 密钥空间为 。以当前最快的超级计算机 Frontier(1.68 ExaFLOP/s)为参考:
| 密钥长度 | 密钥数量 | 56-bit DES破解时间 | 128-bit AES破解时间 | 256-bit AES破解时间 |
|---|---|---|---|---|
| 56 bit | 24小时 | — | — | |
| 128 bit | 年 | — | — | |
| 256 bit | — | 年 | — |
这就是为什么现代密码学认为 128-bit 密钥对暴力破解是计算上不可行的。
流密码不处理固定大小的数据块,而是逐字节(或逐bit)生成密钥流,与明文进行 XOR。
密钥流生成器 ← 密钥 K
│
▼ 逐字节生成
密钥流: k₁, k₂, k₃, ..., kₙ
明文: p₁, p₂, p₃, ..., pₙ
XOR: c₁, c₂, c₃, ..., cₙ ← 密文
| 算法 | 密钥长度 | 设计特点 | 状态 |
|---|---|---|---|
| RC4 | 40-2048 bit | 简单、快速、广泛使用 | ❌ 已弃用(存在严重偏差,前几字节可预测) |
| ChaCha20 | 256 bit | Bernstein设计,高性能,安全 | ✅ 推荐(被TLS 1.3支持) |
| Salsa20 | 256 bit | ChaCha20的前身 | ✅ 安全 |
| Grain-128a | 128 bit | 轻量级(硬件高效) | ✅ 用于IoT场景 |
| AES-CTR | 取决于AES | 分组密码用作流密码 | ✅ 最常用 |
ChaCha20 的工作原理:
ChaCha20 基于一个 512-bit 的状态矩阵(16个32-bit字),分为:
状态矩阵(4×4):
┌──────────┬──────────┬──────────┬──────────┐
│ "expand" │ 32-bit │ 32-bit │ 32-bit │ ← 常数(4字)
│ 密钥 K0 │ 密钥 K1 │ 密钥 K2 │ 密钥 K3 │ ← 密钥(8字)
│ 密钥 K4 │ 密钥 K5 │ 密钥 K6 │ 密钥 K7 │
│ 计数器 │ nonce0 │ nonce1 │ nonce2 │ ← 计数器+nonce(4字)
└──────────┴──────────┴──────────┴──────────┘
每一轮执行"列轮"和"对角线轮"两种变换,ChaCha20 执行 20 轮(10个 列+对角线 对)。
混沌函数(Quarter Round):
a += b; d ^= a; d <<<= 16
c += d; b ^= c; b <<<= 12
a += b; d ^= a; d <<<= 8
c += d; b ^= c; b <<<= 7
ChaCha20 的优势:
性能对比(以 1MB 数据加密,Intel Xeon E5-2680 v3):
| 算法 | 加密速度 | 解密速度 | 每次加密的CPU周期数 |
|---|---|---|---|
| AES-128-GCM (AES-NI) | 3.2 GB/s | 3.1 GB/s | 32 |
| ChaCha20-Poly1305 | 1.8 GB/s | 1.9 GB/s | 57 |
| AES-128-CBC (纯软件) | 0.4 GB/s | 0.3 GB/s | 256 |
| RC4 | 0.8 GB/s | 0.8 GB/s | 128 |
注:使用 AES-NI(硬件指令集扩展)时,AES 速度远超所有纯软件实现的算法。但如果没有硬件加速,ChaCha20 反而更快。
分组密码的基本操作加密的是固定大小的数据块,而实际数据长度通常不是分组的整数倍。因此需要加密模式来扩展使用。
最简单的模式:每块独立加密。
明文块1 → AES加密 → 密文块1
明文块2 → AES加密 → 密文块2
明文块3 → AES加密 → 密文块3
明确不推荐使用 ❌
问题:相同的明文块产生相同的密文块,无法隐藏数据模式。
安全风险:如果密文 ,则明文 。攻击者可以从密文的重复模式推断出明文结构。
真实案例:ECB 加密的图像仍然能看到轮廓
以一个 256 级灰度图像为例,假设像素值表示如下:
原始像素矩阵(8×8):
[[147, 148, 149, ..., 210, 211, 212],
[147, 148, 150, ..., 210, 212, 213],
...]
AES-ECB 加密后,相同的像素值(如 147)会加密为相同的密文值,结果图像虽然"打乱"了具体颜色值,但轮廓完全保留——这就是著名的"企鹅图"问题。
每个密文块参与下一块的加密,提供扩散。
C₀ = IV(初始化向量)
Cᵢ = Eₖ(Pᵢ ⊕ Cᵢ₋₁) ← 加密
Pᵢ = Dₖ(Cᵢ) ⊕ Cᵢ₋₁ ← 解密
加密:
IV → ⊕ → AES → C₁ → ⊕ → AES → C₂ → ⊕ → AES → C₃
↑ ↑ ↑
P₁ P₂ P₃
解密:
IV → ⊕ ← AES ← C₁ → ⊕ ← AES ← C₂ → ⊕ ← AES ← C₃
↓ ↓ ↓
P₁ P₂ P₃
| 特性 | 说明 |
|---|---|
| IV 要求 | 必须随机生成,每次加密不同;固定 IV 会导致相同明文→相同密文 |
| 并行解密 | ✅ 支持(解密时只需 和 ) |
| 并行加密 | ❌ 不支持(需前一块密文) |
| 错误传播 | 一个密文块损坏会影响当前块和下块 |
| 填充要求 | 需要 PKCS#7 等填充方案 |
CBC 模式的具体数值例子:
假设 AES-128 加密,第一个明文块 ,
IV 。
填充示例(PKCS#7):
如果明文长度为 13 字节(AES 分组 16 字节),需要填充 3 字节:
明文(13字节): 48 65 6C 6C 6F 20 57 6F 72 6C 64 21 21
H e l l o W o r l d ! !
填充后(16字节): 48 65 6C 6C 6F 20 57 6F 72 6C 64 21 21 03 03 03
^^^ 填充3个0x03
如果明文正好 16 字节,则追加一整块 16 个 0x10(16字节的填充块)。
将分组密码转为流密码:加密递增的计数器值,与明文 XOR。
C_i = P_i ⊕ Eₖ(Nonce || Counter)
计数器序列:Counter, Counter+1, Counter+2, ...
↓ ↓ ↓
Nonce → AES加密 → AES加密 → AES加密 → ...
↑ ↓ ↓ ↓
密钥 → 密钥流1 ⊕ 密钥流2 ⊕ 密钥流3 ⊕ ...
↓ ↓ ↓
P₁ P₂ P₃
↓ ↓ ↓
C₁ C₂ C₃
| 特性 | 说明 |
|---|---|
| 并行性 | ✅ 加密解密均可完全并行 |
| IV/Nonce 要求 | 每个密钥的 Nonce 必须唯一,否则密钥流重复 |
| 错误传播 | 无(一个密文块损坏只影响对应明文块) |
| 填充 | 不需要(流密码模式) |
| 随机访问 | ✅ 可解密任意块而不需处理整条消息 |
Nonce 唯一性的重要性:
如果同一个密钥 和同一个 Nonce 加密了两条消息:
C₁ = P₁ ⊕ Eₖ(N || 0) || Eₖ(N || 1) || ...
C₂ = P₂ ⊕ Eₖ(N || 0) || Eₖ(N || 1) || ...
则 ,攻击者可以轻易恢复出 和 的信息。这就是著名的 Nonce 重用攻击。
CTR 模式 + 认证标签(GMAC)。GCM 是目前最推荐的加密模式,同时提供机密性和认证性。
GCM 输入:
- 明文 P
- 附加认证数据 AAD(不加密但验证)
- 密钥 K
- Nonce N(96 bit 推荐)
GCM 输出:
- 密文 C(长度 = 明文长度)
- 认证标签 T(128 bit)
Nonce || Counter=0 → AES → GHASH初始化 (H)
Nonce || Counter=1 → AES → 密钥流1 → ⊕ P₁ → C₁
Nonce || Counter=2 → AES → 密钥流2 → ⊕ P₂ → C₂
...
最后认证:GHASH(AAD, C₁...Cₙ, len(AAD)||len(C)) ⊕ Eₖ(N||0) → 认证标签 T
AES-GCM 的认证优势:
| 攻击场景 | 仅加密(如 CBC) | GCM |
|---|---|---|
| 篡改密文 | 解密后产生乱码,但接收方无法区分是否是恶意篡改 | 验证标签失败,直接拒绝 |
| 重放攻击 | 需要应用层处理 | 需要应用层实现 Nonce 唯一性 |
| 填充预言机攻击 | ❌ 易受攻击(如 Lucky 13 攻击) | ✅ Nonce 不使用填充 |
| 速度(带AES-NI) | 约 1.5 GB/s | 约 3.2 GB/s(硬件加速) |
GCM 的 Nonce 安全边界:
当使用 96-bit Nonce 时,对于同一个密钥,加密消息数量不应超过 条。这是因为:
| 模式 | 机密性 | 认证性 | 抗篡改 | 推荐用途 |
|---|---|---|---|---|
| ECB | ❌ | ❌ | ❌ | 不应使用 |
| CBC | ✅(需IV随机) | ❌ | ❌ | 遗留系统兼容 |
| CFB | ✅ | ❌ | ❌ | 不常用 |
| OFB | ✅ | ❌ | ❌ | 不常用 |
| CTR | ✅(需Nonce唯一) | ❌ | ❌ | 需要并行时 |
| GCM | ✅ | ✅ | ✅ | 强烈推荐 |
| CCM | ✅ | ✅ | ✅ | 无线网络(802.11) |
| XTS | ✅ | ❌ | ❌ | 磁盘加密专用 |
安全随机数是密钥生成的基础。绝对不能使用伪随机数生成器(如 C 的 rand())生成密钥。
| 方法 | 来源 | 安全性 | 用途 |
|---|---|---|---|
| /dev/urandom | Linux 内核熵池 | ✅ 高 | 通用(推荐) |
| /dev/random | Linux 内核熵池 | ✅ 高(可能阻塞) | 安全关键场景 |
| CryptGenRandom | Windows API | ✅ 高 | Windows 平台 |
| SecureRandom (Java) | 操作系统+混合 | ✅ 高 | Java 应用 |
| C rand() | 线性同余 | ❌ 可预测 | ❌ 不可用于密码学 |
| Python random | Mersenne Twister | ❌ 可预测 | ❌ 不可用于密码学 |
验证示例:rand() 的线性同余生成器可以通过连续几个输出值完全预测后续所有值,因此用其生成的"密钥"可被攻击者瞬间破解。
从密码或弱熵源生成强密钥:
密码 "MyP@ssw0rd" → KDF → AES-256 密钥
↓
PBKDF2 + 盐值(salt) + 迭代次数(iterations)
| KDF 算法 | 迭代次数 | 抗暴力破解 | 推荐用途 |
|---|---|---|---|
| PBKDF2 | 600,000+ | 中等(CPU密集) | 密码存储(遗留) |
| bcrypt | 12+轮 | 好(CPU+内存) | 密码存储 |
| scrypt | N=2^14, r=8, p=1 | 更好(CPU+内存+并行) | 密码存储 |
| Argon2id | m=19000, t=2, p=1 | 最好(CPU+内存+并行+侧信道抵抗) | 当前推荐 |
Argon2id 使用示例:
# 从密码 "MyP@ssw0rd" 派生 AES-256 密钥
# 参数:内存 19MB,迭代 2次,并行度 1
salt = os.urandom(16) # 128-bit 随机盐值
key = hashlib.pbkdf2_hmac('sha256', b'MyP@ssw0rd', salt, 600000, dklen=32)
# key 就是 256-bit(32字节)的 AES 密钥
对称加密的"阿喀琉斯之踵"——如何安全地将密钥传递给对方?
| 方法 | 安全性 | 适用场景 | 备注 |
|---|---|---|---|
| 预先共享 | 取决于信道 | 面对面交换 | 最简单但扩展性差 |
| Diffie-Hellman | ✅ 高 | 在线密钥交换 | 用于 TLS |
| 非对称加密密钥分发 | ✅ 高 | 混合加密(如TLS) | 最常用 |
| KDC(密钥分发中心) | ✅ 中 | 企业内网 | Kerberos |
| 量子密钥分发(QKD) | ✅ 理论上无条件安全 | 高安全场景 | 成本高 |
实际工程实践(混合加密方案):
这就是 TLS 的核心工作方式,平衡了非对称加密的安全分发和对称加密的高效性。
| 数据敏感性 | 建议轮换周期 | 最大加密量(AES-GCM) | 理由 |
|---|---|---|---|
| 普通网页 | 24小时 | < 条消息 | 平衡性能和安全 |
| 金融交易 | 2小时 | < 条消息 | 高频数据需更短周期 |
| 军事机密 | 每次会话 | < 条消息 | 最大安全性 |
| 长期存储 | 每年 | < 条消息 | 归档数据低频访问 |
当明文长度不是分组大小(AES 为 16 字节)的整数倍时,需要填充。
最常用的填充方案:
填充值 = 需要填充的字节数
| 明文长度 (mod 16) | 填充字节数 | 填充值 | 填充后长度 |
|---|---|---|---|
| 0(正好对齐) | 16 | 0x10 × 16 | 原长度 + 16 |
| 1 | 15 | 0x0F × 15 | 原长度 + 15 |
| 2 | 14 | 0x0E × 14 | 原长度 + 14 |
| ... | ... | ... | ... |
| 15 | 1 | 0x01 × 1 | 原长度 + 1 |
填充有效性验证:
解密后检查最后一个字节 (),确认末尾 个字节的值都是 。如果验证失败,说明密文被篡改或密钥不正确。
⚠️ 填充验证不能泄露具体是"填充格式错误"还是"解密失败"——这就是填充预言机攻击(Padding Oracle Attack)的利用点。CBC 模式配合不良的错误处理,攻击者可以通过试错逐字节恢复明文。
| 方案 | 方式 | 特点 |
|---|---|---|
| PKCS#7 | 填充值为补足字节数 | 最常用 |
| ISO 10126 | 末尾为补足字节数,其余随机 | 已弃用 |
| ANSI X.923 | 末尾为补足字节数,其余为0 | 已弃用 |
| Zero Padding | 填充0x00 | 不唯一(无法区分真实0x00和填充) |
| CTS(Ciphertext Stealing) | 不填充,通过密文窃取 | 适用于部分场景,不通用 |
| 年份 | 目标 | 计算资源 | 结果 |
|---|---|---|---|
| 1998 | DES-56bit | $250,000 专用硬件 | 56小时破解 |
| 2008 | DES-56bit | FPGA集群 | 1天 |
| 2012 | DES-56bit | 商业云服务 | 不到24小时 |
| 至今 | AES-128/256 | 所有已知计算机 | 不可行 |
| 攻击类型 | 目标 | 利用手段 | 防御措施 |
|---|---|---|---|
| 时序攻击 | AES 软件实现 | 测量加密时间推测密钥 | 常数时间实现 |
| 缓存时间攻击 | AES(S表查找) | 测量缓存命中/未命中延迟 | 使用 AES-NI 指令集 |
| 功耗分析 | 智能卡、嵌入式设备 | 分析加密时的功耗曲线 | 加噪、平衡电路 |
| 电磁分析 | 近距离设备 | 捕获电磁泄漏 | 屏蔽、减少泄漏 |
缓存时间攻击的工作原理:
以 AES 的 S 盒查找为例,CPU 缓存通常为 64 字节一行。当攻击者通过共享内存或超线程与加密进程共享同一 CPU 时:
防御方案:使用 AES-NI 硬件指令,它在硬件层面执行加密,完全在 CPU 内部完成,不涉及内存 S 表查找。
对 CBC 模式的有效攻击。攻击者通过观察服务器是否返回"填充错误"来逐字节破解密文。
攻击步骤(以解密字节 为例):
假设目标:破解 C₂ 的最后一个字节
攻击者修改 C₁(IV)的最后一个字节 C₁[15],反复尝试 0x00 到 0xFF
当服务器不再返回"填充错误"时,求解出 P₂[15]
防御:
| 用途 | 推荐算法 | 密钥长度 | 模式 |
|---|---|---|---|
| 通用数据加密 | AES | 256 bit | GCM |
| 性能优先(无AES-NI) | ChaCha20 | 256 bit | Poly1305 |
| 磁盘加密 | AES | 256 bit | XTS |
| 数据库列加密 | AES | 256 bit | GCM |
| IoT/嵌入式 | AES | 128 bit | CCM |
| TLS 1.3 连接 | ChaCha20 或 AES-GCM | 256 bit | 自动协商 |
| 中国国密标准 | SM4 | 128 bit | GCM |
推荐组合示例(用于 Web 应用):
加密:AES-256-GCM
密钥派生:Argon2id
密钥长度:256 bit
Nonce:96-bit 随机(每次加密新生成)
认证标签:128 bit
密钥轮换:每 24 小时或每 2^30 条消息
原始文件 → AES-256-GCM 加密 → 加密文件(含认证标签)
↑
用户密码 → Argon2id → 256-bit密钥
使用 OpenSSL 加密文件:
# 加密
openssl enc -aes-256-gcm -salt -in secret.txt -out secret.txt.enc -pbkdf2 -iter 600000
# 解密
openssl enc -d -aes-256-gcm -in secret.txt.enc -out secret.txt -pbkdf2 -iter 600000
| 压缩前 | 加密后 | 加密+压缩后 | 推荐 |
|---|---|---|---|
| 10 MB | 10 MB(+16字节标签) | 3 MB | ✅ 先压缩后加密 |
| 加密层级 | 加密程度 | 性能影响 | 密钥管理 | 典型方案 |
|---|---|---|---|---|
| 全盘加密 | 整个磁盘 | <5% | 操作系统密钥 | LUKS + AES-XTS |
| 文件系统加密 | 文件粒度 | <10% | 登录密码派生 | eCryptfs、fscrypt |
| 数据库透明加密(TDE) | 数据文件 | <15% | 数据库管理 | MySQL TDE、SQL Server TDE |
| 列级别加密 | 敏感列 | 20-50% | 应用管理 | AES-256-GCM 应用层加密 |
| 字段级别加密 | 个别字段 | 50-200% | 客户端管理 | 端到端加密 |
MySQL/Aurora 列级加密示例:
-- 创建加密函数
CREATE FUNCTION aes_encrypt_column(plaintext VARCHAR(255), key VARBINARY(32))
RETURNS VARBINARY(64) DETERMINISTIC
BEGIN
SET @nonce = RANDOM_BYTES(12);
SET @ciphertext = AES_ENCRYPT(plaintext, key, @nonce, 'GCM');
RETURN CONCAT(@nonce, @ciphertext); -- nonce + 密文 + 标签
END;
TLS 1.3 的密钥协商和加密流程使用混合加密(非对称+对称):
阶段1:密钥交换(非对称)
ClientHello →
← ServerHello + 证书
ECDHE 密钥协商
→ 交换的 DH 参数计算出共享密钥
阶段2:对称加密数据传输
应用数据 → AES-256-GCM 加密 →
TLS 1.3 支持的对称加密套件:
TLS_AES_128_GCM_SHA256TLS_AES_256_GCM_SHA384TLS_CHACHA20_POLY1305_SHA256LUKS 使用 XTS 模式进行全盘加密。XTS 模式的特点:
| 硬盘大小 | LUKS 加密速度(AES-XTS-256,有AES-NI) | 解密速度 | 性能开销 |
|---|---|---|---|
| 256 GB SSD | 380 MB/s | 375 MB/s | ~3% |
| 1 TB NVMe | 2800 MB/s | 2750 MB/s | ~5% |
| 4 TB HDD | 180 MB/s | 175 MB/s | ~2% |
AES 胜出的原因:
| 年代 | 主流算法 | 密钥长度 | 安全等级 | 备注 |
|---|---|---|---|---|
| 1970s | DES | 56 bit | ❌ | 已被破解 |
| 1990s | 3DES | 112 bit | ⚠️ | 正在弃用 |
| 2000s | AES | 128-256 bit | ✅ | 当前标准 |
| 2010s | ChaCha20 | 256 bit | ✅ | 移动端/无AES-NI场景 |
| 2020s | AES+ChaCha20 | 256 bit | ✅ | 两者并存 |
| 未来 | 后量子对称密码 | 256+ bit | 待评估 | 哈希长度加倍以抗 Grover 算法 |
/dev/urandom、CryptGenRandom)错误做法(可变时间):
def verify_tag(tag1, tag2):
return tag1 == tag2 # 遇到第一个不同字节时提前退出
正确做法(常数时间):
def constant_time_compare(tag1, tag2):
if len(tag1) != len(tag2):
return False
result = 0
for a, b in zip(tag1, tag2):
result |= ord(a) ^ ord(b)
return result == 0