对称加密基础与AES:一把钥匙开一把锁
对称加密,听名字就知道——加密和解密用同一把密钥。你想想看,就像你用一把钥匙锁上抽屉,再用同一把钥匙打开它。这是最古老也最直觉的加密方式。我在项目里跟非技术同事解释时,就举这个例子,他们秒懂。
对称加密的核心三要素
对称加密要跑起来,离不开三样东西:明文、密钥、算法。明文是你想藏起来的数据,密钥是那把唯一的钥匙,算法就是锁芯。算法的强度决定了你这把锁容不容易被撬。
我个人习惯把对称加密分成两类:
- 流密码:逐位加密,像一条流动的河。典型代表是 RC4,但现在已经不推荐用了。我在一个旧项目中处理过遗留的 RC4 加密,查出来的时候吓一跳——那玩意儿早就被攻破了。
- 分组密码:把明文切成固定大小的块,一块一块处理。AES 就是最典型的分组密码,块大小固定 128 位。
为什么要分组?因为现实中数据是连续且变长的,不能指望每段消息都刚好是块大小的整数倍。于是就有了填充模式——这是后来很多人踩坑的地方。
AES:当今的行业标准
AES(Advanced Encryption Standard)是 2001 年 NIST 正式发布的标准。它取代了老旧的 DES,成为全球用得最广的对称加密算法。你手机上、浏览器里、银行转账——背后几乎都有 AES。
AES 支持三种密钥长度:128 位、192 位、256 位。位数越高越安全,但计算开销也越大。我怎么选?
| 密钥长度 | 轮数 | 安全等级 | 性能表现 |
|---|---|---|---|
| AES-128 | 10 轮 | 足够安全(除量子攻击外) | 最快 |
| AES-192 | 12 轮 | 更高 | 稍慢 |
| AES-256 | 14 轮 | 最高 | 最慢 |
AES 的工作模式:不止是拧一下钥匙
AES 本身只规定了一个块怎么加解密。但如果每个块都独立加解密,相同明文就会产生相同密文——攻击者能看出规律。这就是为什么我们需要工作模式。
我归纳了最常见的几种:
- ECB(电子密码本模式)——最糟糕的老模式,每个块独立加密。我曾在项目中接手一个用 ECB 加密的图片文件,密文里还能看出原图的轮廓。别用 ECB,永远别。
- CBC(密码分组链接模式)——每个块在加密前先和前一个密文块异或。需要初始化向量(IV)。安全性比 ECB 强很多,但并行性差。
- CTR(计数器模式)——把 AES 当作流密码用。每秒能处理海量数据,支持并行。我参与的一个大数据管道就用 CTR,吞吐量很满意。
- GCM(伽罗瓦/计数器模式)——CTR 加上身份认证。一次操作同时完成加密和校验完整性。说白了,它不仅能防泄密,还能防篡改。这是我现在最推荐的标准模式。
| 模式 | 并行性 | 完整性保护 | 常见用途 |
|---|---|---|---|
| ECB | ✅ 支持 | ❌ 无 | 已废弃,仅做测试 |
| CBC | ❌ 不支持 | ❌ 无 | 传统协议(如 SSL 3.0) |
| CTR | ✅ 支持 | ❌ 无 | 高性能场景 |
| GCM | ✅ 支持 | ✅ 支持 | 现代 TLS / VPN / 密文检索 |
填充与 IV:最容易出错的细节
明文长度不一定正好是 16 字节的整数倍。这时候需要填充。PKCS7 是最常见的方案——缺几个字节就补几,如果恰好够 16 字节,则补一整块 16。听起来简单,但实现时没写对会导致解密密文失败。我曾经调试过一整个下午,最后发现是填充校验写反了。
IV(初始化向量)在 CBC、CTR、GCM 里都必须使用。它不需要保密,但不能重用。同一把密钥下,IV 重复一次,就可能被攻击者恢复明文。我建议用 12 字节随机数 + 4 字节计数器的组合(GCM 的做法)。
AES 的底层结构(概览)
虽然咱们不是实现算法,但理解它的骨架能帮你判断密钥边界。AES 的核心操作包括:
- SubBytes:用 S 盒替换每个字节
- ShiftRows:行移位,打乱顺序
- MixColumns:列混合,扩散字节
- AddRoundKey:轮密钥异或
每一轮把这四种操作串起来。密钥从初始密钥通过密钥扩展算法生成每一轮需要的子密钥。
下面这张图展示了 AES-128 的整体流程,把密钥边界和加密路径理清楚:
从图中你可以看出:密钥只在 AddRoundKey 这一步介入,不参与非线性替换和扩散。这意味着密钥的强度依赖扩展算法的随机性。一旦初始密钥泄漏,整条链全完。所以密钥的生成、存储、分发才是真正的边界约束。
代码示例:使用 OpenSSL 做 AES-GCM 加密
纸上得来终觉浅。我贴一段实际工作中写的 C 代码片段,用了 OpenSSL 的 EVP 接口。GCM 模式自带认证标签,非常适合密文检索场景。
#include <openssl/evp.h>
#include <string.h>
int aes_gcm_encrypt(const unsigned char *plaintext, int plaintext_len,
const unsigned char *key, const unsigned char *iv,
unsigned char *ciphertext, unsigned char *tag) {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (!ctx) return -1;
// 使用 AES-256-GCM
if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL))
goto err;
// 设置 IV 长度(GCM 推荐 12 字节)
if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL))
goto err;
// 初始化密钥和 IV
if (1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv))
goto err;
int len = 0;
// 加密
if (1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
goto err;
int ciphertext_len = len;
// 结束加密
if (1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len))
goto err;
ciphertext_len += len;
// 获取认证标签
if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag))
goto err;
EVP_CIPHER_CTX_free(ctx);
return ciphertext_len;
err:
EVP_CIPHER_CTX_free(ctx);
return -1;
}
EVP_CTRL_GCM_SET_IVLEN 明确设置 IV 长度,不要依赖缺省值。另外,tag 的长度取 16 字节(最多 16),但在传输时建议至少带上 12 字节。
对称加密在密文检索中的挑战
我们这门课叫《密文检索场景下密钥边界约束方案》。对称加密在密文检索里有一个天然矛盾:加密后数据失去可比较性。如果直接用 AES 加密每个字段,你没法在密文上做范围查询、关键词匹配。所以需要引入保留格式加密、顺序保留加密或者安全索引。
但无论用什么高级方案,密钥的边界定义始终是第一道防线。密钥放在哪?谁生成?怎么轮换?这些是架构层面必须回答的问题。对称加密本身很成熟,出错的地方往往在边界——比如密钥被埋在代码里、IV 重复使用、密钥长度选错。
我曾经在一个项目里看到团队用了 AES-256-CBC,密钥明文写死在配置文件里。我当时就问:你们管这个叫安全?后来他们换了密钥管理服务,才过了合规审计。
对称加密是一门基础但需要敬畏的手艺。AES 算法本身固若金汤,但密钥和 IV 的管理才是真正考验工程师的地方。下一层我们需要深入密钥派生和密钥协商,但那是后话了。先把基础打牢,才能盖好上层建筑。