文章

深入理解 AES 加密:从原理到 SQL 实战

深入理解 AES 加密:从原理到 SQL 实战

简介

AES(Advanced Encryption Standard,高级加密标准)是目前应用最广泛的对称加密算法,由 NIST 于 2001 年发布(FIPS 197),基于 Rijndael 算法。无论是数据库字段加密、文件加密还是 HTTPS 底层通信,AES 都是事实标准。

本文将从核心概念出发,结合 MySQLSpark 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-1281610足够大多数场景
AES-1922412较高
AES-2563214最高,推荐敏感数据

加密模式对比

模式是否需要 IV是否推荐特点
ECB不推荐相同明文 → 相同密文,易泄漏数据模式
CBC是(16 字节)推荐链式加密,需配合 PKCS5/PKCS7 填充
GCM是(12 字节)强烈推荐兼顾机密性 + 完整性(自带认证标签),无需额外填充

选型建议:新项目优先使用 GCM 模式;存量系统至少升级到 CBC绝不在生产环境使用 ECB

IV(初始向量)

  • CBC 模式:IV 长度 = 块大小 = 16 字节
  • GCM 模式:IV 长度推荐 12 字节
  • 每次加密必须使用不同的随机 IV,并将 IV 与密文一起存储(IV 本身不需要保密)

编码

数据库的加密函数通常返回二进制结果。存入文本字段或通过 API 传输前,需使用 Base64HEX 编码;解密前需做反向解码。


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 编码。

进一步阅读

本文由作者按照 CC BY 4.0 进行授权