深入理解 AES 加密:从原理到 SQL 实战
简介
AES(Advanced Encryption Standard,高级加密标准)是目前应用最广泛的对称加密算法,由 NIST 于 2001 年发布(FIPS 197),基于 Rijndael 算法。无论是数据库字段加密、文件加密还是 HTTPS 底层通信,AES 都是事实标准。
本文将从核心概念出发,结合 MySQL、Spark SQL 以及 Python 的实际代码示例,帮助你快速掌握 aes_encrypt / aes_decrypt 的正确用法,并规避常见安全陷阱。
AES 加密流程概览
flowchart LR
A[明文] -->|密钥 + IV| B(AES 加密)
B --> C[密文(二进制)]
C -->|Base64 / HEX| D[可存储/传输文本]
D -->|Base64 / HEX 解码| E[密文(二进制)]
E -->|密钥 + IV| F(AES 解密)
F --> G[明文]
核心概念
密钥长度
AES 支持三种密钥长度,加密轮数不同,安全强度递增:
| 密钥长度 | 字节数 | 加密轮数 | 安全强度 |
|---|---|---|---|
| AES-128 | 16 | 10 | 足够大多数场景 |
| AES-192 | 24 | 12 | 较高 |
| AES-256 | 32 | 14 | 最高,推荐敏感数据 |
加密模式对比
| 模式 | 是否需要 IV | 是否推荐 | 特点 |
|---|---|---|---|
| ECB | 否 | 不推荐 | 相同明文 → 相同密文,易泄漏数据模式 |
| CBC | 是(16 字节) | 推荐 | 链式加密,需配合 PKCS5/PKCS7 填充 |
| GCM | 是(12 字节) | 强烈推荐 | 兼顾机密性 + 完整性(自带认证标签),无需额外填充 |
选型建议:新项目优先使用 GCM 模式;存量系统至少升级到 CBC;绝不在生产环境使用 ECB。
IV(初始向量)
- CBC 模式:IV 长度 = 块大小 = 16 字节
- GCM 模式:IV 长度推荐 12 字节
- 每次加密必须使用不同的随机 IV,并将 IV 与密文一起存储(IV 本身不需要保密)
编码
数据库的加密函数通常返回二进制结果。存入文本字段或通过 API 传输前,需使用 Base64 或 HEX 编码;解密前需做反向解码。
MySQL 示例
MySQL 8+ 提供 AES_ENCRYPT(str, key_str [, init_vector]) / AES_DECRYPT(crypt_str, key_str [, init_vector]),加密模式由系统变量 block_encryption_mode 决定。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- 1. 设置加密模式为 AES-256-CBC(会话级)
SET block_encryption_mode = 'aes-256-cbc';
-- 2. 加密:返回二进制密文
SELECT AES_ENCRYPT('Hello AES', 'mys3cretKey12345678901234', '0123456789abcdef') AS cipher_blob;
-- 3. 转 Base64 便于存储
SELECT TO_BASE64(
AES_ENCRYPT('Hello AES', 'mys3cretKey12345678901234', '0123456789abcdef')
) AS cipher_b64;
-- 4. 解密(先 Base64 解码再解密)
SELECT CAST(
AES_DECRYPT(
FROM_BASE64('上一步得到的Base64字符串'),
'mys3cretKey12345678901234',
'0123456789abcdef'
) AS CHAR
) AS plain_text;
密钥长度必须与
block_encryption_mode匹配:aes-128→ 16 字节,aes-192→ 24 字节,aes-256→ 32 字节。
Spark SQL 示例
Spark 3.3+ 内置 aes_encrypt / aes_decrypt,函数签名:
1
2
aes_encrypt(expr, key [, mode [, padding [, iv [, aad]]]])
aes_decrypt(expr, key [, mode [, padding [, aad]]])
mode 可选值:'ECB'、'CBC'、'GCM'(默认 'GCM');padding 可选值:'PKCS'、'NONE'、'DEFAULT'。
GCM 模式(推荐)
1
2
3
4
5
6
7
-- GCM 模式加密 & 解密(Spark 默认即 GCM)
SELECT
base64(aes_encrypt('Spark AES', '0000111122223333')) AS cipher_b64,
cast(aes_decrypt(
aes_encrypt('Spark AES', '0000111122223333'),
'0000111122223333'
) AS STRING) AS plain_back;
CBC 模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- CBC 模式需要显式提供 IV(16 字节)
SELECT
base64(
aes_encrypt('Spark AES', 'abcdefghijklmnop12345678ABCDEFGH', 'CBC', 'DEFAULT',
unhex('00000000000000000000000000000000'))
) AS cipher_b64;
-- 解密
SELECT cast(
aes_decrypt(
unbase64('上一步得到的Base64字符串'),
'abcdefghijklmnop12345678ABCDEFGH', 'CBC', 'DEFAULT'
) AS STRING
) AS plain_text;
Spark 的 GCM 模式会自动生成随机 IV 并拼接到密文头部,解密时自动提取;CBC 模式同理。因此不需要单独管理 IV 的存储。
Python 示例
使用 cryptography 库实现 AES-256-GCM:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os, base64
# 生成 256-bit 密钥(32 字节)
key = AESGCM.generate_key(bit_length=256)
# 加密
nonce = os.urandom(12) # GCM 推荐 12 字节 nonce
aesgcm = AESGCM(key)
plaintext = "需要加密的敏感数据".encode("utf-8")
ciphertext = aesgcm.encrypt(nonce, plaintext, None) # 第三个参数为 AAD(可选)
# 拼接 nonce + 密文,Base64 编码后存储
token = base64.b64encode(nonce + ciphertext).decode()
print(f"加密结果: {token}")
# 解密
raw = base64.b64decode(token)
nonce_dec, ct = raw[:12], raw[12:]
decrypted = aesgcm.decrypt(nonce_dec, ct, None)
print(f"解密结果: {decrypted.decode('utf-8')}")
安装依赖:
pip install cryptography
常见坑与最佳实践
| 问题 | 说明 | 建议 |
|---|---|---|
| 使用 ECB 模式 | 相同明文 → 相同密文,攻击者可推断数据模式 | 使用 CBC 或 GCM |
| IV/Nonce 重复 | 尤其在 GCM 下重用 nonce 会完全破坏安全性 | 每次加密生成随机 IV |
| IV 长度错误 | CBC 需 16 字节;GCM 需 12 字节 | 严格按规范设置 |
| 密文当文本存储 | 二进制写入 VARCHAR 会截断或乱码 | 先 Base64/HEX 编码 |
| 密钥硬编码 | 写在源码、日志或前端中极易泄漏 | 使用密钥管理服务(KMS)或环境变量 |
| 缺少完整性校验 | CBC 模式不提供认证,可能遭受填充预言攻击 | 优先使用 GCM,或 CBC + HMAC |
总结
- AES 是对称加密的首选算法,密钥长度建议 256 bit。
- 模式选择:GCM > CBC » ECB(ECB 仅可用于测试)。
- 每次加密使用随机 IV,与密文一起保存。
- 密钥通过 KMS / Vault / 环境变量管理,杜绝硬编码。
- 数据库/SQL 函数返回二进制,存储前做 Base64 / HEX 编码。