深入理解 AES 加密:从原理到 SQL 实战
简介
AES(Advanced Encryption Standard,高级加密标准)是广泛使用的对称加密算法,由 NIST 在 FIPS 197 中标准化。数据库字段和文件加密都可能用到 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 字节) | 仅在配合独立认证时使用 | 需填充;本身不校验密文完整性 |
| GCM | 是(通常 12 字节 nonce) | 新方案优先考虑 | 同时提供保密性和完整性,输出认证标签 |
新方案优先选用 GCM 等带认证的模式。CBC 本身不提供完整性保护,若必须使用,需要另行认证;不要把“改用 CBC”当成完整的安全升级。ECB 不适合一般数据加密。
IV(初始向量)
- CBC 模式:IV 长度 = 块大小 = 16 字节
- GCM 模式:IV 长度推荐 12 字节
- GCM:在同一密钥下不能重复使用 nonce;通常为每次加密生成新的 12 字节随机 nonce
- CBC:每次加密使用新的、不可预测的 IV
- IV/nonce 通常与密文一起保存,本身不需要保密
编码
数据库的加密函数通常返回二进制结果。存入文本字段或通过 API 传输前,需使用 Base64 或 HEX 编码;解密前需做反向解码。
MySQL 示例
MySQL 8+ 提供 AES_ENCRYPT(str, key_str [, init_vector]) / AES_DECRYPT(crypt_str, key_str [, init_vector]),加密模式由系统变量 block_encryption_mode 决定。
下面仅演示同一会话中如何加解密。密钥材料和 IV 都临时生成,避免把固定值写进示例并反复用于真实数据:
1
2
3
4
5
6
7
8
9
10
11
12
-- 设置加密模式(会话级)
SET block_encryption_mode = 'aes-256-cbc';
SET @key_str = RANDOM_BYTES(32);
SET @iv = RANDOM_BYTES(16);
SET @cipher = AES_ENCRYPT('Hello AES', @key_str, @iv);
-- 文本传输时编码;也可以直接存入 VARBINARY/BLOB 字段
SELECT TO_BASE64(@cipher) AS cipher_b64;
-- 使用同一份密钥材料和 IV 解密
SELECT CAST(AES_DECRYPT(@cipher, @key_str, @iv) AS CHAR) AS plain_text;
这只是会话内演示,
@key_str和@iv随会话结束而消失。真实应用需要安全管理密钥材料,并为每条密文保存相应的 IV;MySQL 的key_str也不能简单理解为“输入字节数必须等于 AES 密钥长度”。此外,CBC 不提供认证,不能单独作为新系统的完整加密方案。详见 MySQL 官方文档。
Spark SQL 示例
Spark 3.3 起提供 aes_encrypt / aes_decrypt;下面的 CBC 示例需要支持 CBC 模式的 Spark 3.5 或更新版本。函数签名:
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
-- 不显式指定 IV 时,Spark 会生成随机 IV 并放在密文前
SELECT
base64(
aes_encrypt('Spark AES', 'abcdefghijklmnop12345678ABCDEFGH', 'CBC', 'DEFAULT')
) AS cipher_b64;
-- 解密
SELECT cast(
aes_decrypt(
unbase64('上一步得到的Base64字符串'),
'abcdefghijklmnop12345678ABCDEFGH', 'CBC', 'DEFAULT'
) AS STRING
) AS plain_text;
在这些示例中,未指定 IV 时 Spark 会生成并附加 IV,解密函数会从密文中读取它。示例中的固定密钥只为展示函数调用;实际应用应安全生成和管理密钥,且 CBC 还需要独立的完整性保护。参见 Spark 3.5 SQL 函数文档。
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 模式 | 相同明文块会产生相同密文块,泄漏数据模式 | 一般数据加密选用带认证的模式 |
| IV/Nonce 重复 | 同一密钥下重复使用 GCM nonce 会破坏安全性 | 每次加密生成新的 nonce |
| IV 长度错误 | CBC 使用 16 字节 IV;本文 GCM 示例使用 12 字节 nonce | 按所用库和模式的要求设置 |
| 把二进制密文直接写入文本字段 | 字符集转换等处理可能改变数据 | 用二进制字段,或先 Base64/HEX 编码 |
| 密钥硬编码 | 写在源码、日志或前端中容易泄漏 | 使用专门的密钥管理机制 |
| 缺少完整性校验 | CBC 本身不认证密文 | 优先使用 GCM;使用 CBC 时另做认证 |
总结
- AES-128 和 AES-256 都有常见的实际用途;选模式和正确管理密钥同样重要。
- 新方案优先考虑 GCM 等带认证的模式;CBC 需要另行认证,避免用 ECB 加密一般数据。
- GCM 的 nonce 在同一密钥下必须保持唯一;CBC 使用新的、不可预测的 IV。
- 使用合适的密钥管理机制,避免把真实密钥写进源码或示例。
- 二进制密文可存入二进制字段;需要文本传输或存储时再做 Base64/HEX 编码。