密码学中的高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。作为当前最主流的对称加密算法,广泛应用于 HTTPS、云存储、支付系统等场景。
一、AES 核心基础
1. 算法本质与核心特性
AES 是一种对称分组密码,核心特点如下:
-
分组固定:无论密钥长度多少,数据均按 16 字节(128 位)分组处理;
-
密钥可选:支持 128/192/256 位密钥(对应 16/24/32 字节),推荐 128 位(够用且性能最优);
-
安全性强:2001 年标准化至今,无实用攻击方法,NIST 确认其在后量子时代仍可使用;
-
跨平台兼容:所有主流语言、框架均原生支持,适合多系统数据加密传输。
2. 加密模式
AES 本身仅能处理 16 字节固定长度数据,实际应用需配合加密模式处理任意长度数据。主流模式对比:
| 模式 | 是否带认证 | 性能 | 安全性 | 典型场景 | 推荐度 |
|---|---|---|---|---|---|
| GCM | ✅(AEAD) | 极高(硬件加速) | 最高 | HTTPS、API 通信、云存储 | ⭐⭐⭐⭐⭐ |
| CTR | ❌ | 高(并行性好) | 中 | 纯机密性场景(需额外加 HMAC) | ⭐⭐⭐☆☆ |
| CBC | ❌ | 中(并行性差) | 低(易受 Padding Oracle 攻击) | 遗留系统兼容 | ⚠️ 仅推荐遗留场景 |
| ECB | ❌ | 高 | 极差(泄露明文图案) | 无 | ❌ 禁止使用 |
优先使用 GCM 模式
(带数据认证,防篡改、防伪造),无需额外搭配 HMAC,实现更简单、更安全。
3. 填充方式
AES 要求明文长度必须是 16 字节的整数倍,当不满足时需通过填充补足:
-
常用填充:PKCS7,规则是 "缺 n 字节则填充 n 个 0x0n 字节";
-
兼容说明:PKCS5 是 PKCS7 的子集(仅支持 8 字节分组),AES 场景中很多库标注 "PKCS5Padding" 实际执行 PKCS7 逻辑;
-
禁用填充:Zero Padding(用 0x00 填充),无法区分有效数据与填充数据,不推荐使用。
二、Java示例
Java 使用 JDK 原生javax.crypto包实现.
java
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
/**
* AES-GCM模式加密解密工具类
* 特点:带数据认证、防篡改、跨平台兼容
*/
public class AesGcmUtil {
// GCM模式推荐IV长度:12字节
private static final int IV_LENGTH = 12;
// GCM模式认证标签长度:16字节
private static final int TAG_LENGTH = 16;
// 密钥长度:128位(16字节)
private static final int KEY_LENGTH = 128;
/**
* 生成AES密钥(128位)
* @return Base64编码后的密钥字符串
*/
public static String generateKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(KEY_LENGTH, new SecureRandom());
SecretKey secretKey = keyGenerator.generateKey();
return Base64.getEncoder().encodeToString(secretKey.getEncoded());
}
/**
* 加密(GCM模式)
* @param plaintext 明文(任意字符串)
* @param keyStr Base64编码的密钥
* @return Base64编码的密文(格式:IV + 密文 + 认证标签)
*/
public static String encrypt(String plaintext, String keyStr) throws Exception {
// 1. 解析密钥
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
// 2. 生成随机IV(必须随机,不可固定)
byte[] iv = new byte[IV_LENGTH];
new SecureRandom().nextBytes(iv);
// 3. 初始化GCM加密器
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); // GCM模式无需额外填充
GCMParameterSpec gcmSpec = new GCMParameterSpec(TAG_LENGTH * 8, iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
// 4. 加密(生成密文+认证标签)
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// 5. 拼接IV+密文+标签(解密时需要提取IV和标签)
byte[] result = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(ciphertext, 0, result, iv.length, ciphertext.length);
return Base64.getEncoder().encodeToString(result);
}
/**
* 解密(GCM模式)
* @param ciphertextStr Base64编码的密文
* @param keyStr Base64编码的密钥
* @return 明文
*/
public static String decrypt(String ciphertextStr, String keyStr) throws Exception {
// 1. 解析密钥和密文
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
byte[] ciphertext = Base64.getDecoder().decode(ciphertextStr);
// 2. 提取IV和带标签的密文
byte[] iv = new byte[IV_LENGTH];
System.arraycopy(ciphertext, 0, iv, 0, iv.length);
byte[] encryptedData = new byte[ciphertext.length - iv.length];
System.arraycopy(ciphertext, iv.length, encryptedData, 0, encryptedData.length);
// 3. 初始化解密器
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec gcmSpec = new GCMParameterSpec(TAG_LENGTH * 8, iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
// 4. 解密(自动验证标签,篡改会抛出异常)
byte[] plaintextBytes = cipher.doFinal(encryptedData);
return new String(plaintextBytes, StandardCharsets.UTF_8);
}
// 测试方法
public static void main(String[] args) throws Exception {
String key = generateKey();
String plaintext = "AES-GCM加密测试:包含中文、特殊字符!@#$%";
System.out.println("密钥(Base64):" + key);
// 加密
String ciphertext = encrypt(plaintext, key);
System.out.println("加密结果:" + ciphertext);
// 解密
String decrypted = decrypt(ciphertext, key);
System.out.println("解密结果:" + decrypted);
}
}
关键注意点
-
IV(初始化向量)必须随机生成(不可硬编码),长度推荐 12 字节(GCM 模式);
-
GCM 模式无需额外填充(
NoPadding),算法内部自动处理数据长度; -
密文格式采用 "IV + 密文 + 标签" 拼接,解密时需按顺序提取;
-
解密时会自动验证认证标签,数据被篡改会抛出
AEADBadTagException。
三、Python示例
Python 使用pycryptodome库实现
1. 环境准备
pip install pycryptodome # 注意:pycrypto已废弃
python
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
import base64
import hashlib
class AesGcmUtil:
# 与Java保持一致:IV长度12字节,标签长度16字节
IV_LENGTH = 12
TAG_LENGTH = 16
KEY_LENGTH = 16 # 128位密钥(对应16字节)
@staticmethod
def generate_key():
"""生成128位AES密钥(Base64编码)"""
key = get_random_bytes(AesGcmUtil.KEY_LENGTH)
return base64.b64encode(key).decode('utf-8')
@staticmethod
def encrypt(plaintext, key_str):
"""
GCM模式加密
:param plaintext: 明文字符串
:param key_str: Base64编码的密钥
:return: Base64编码的密文(IV+密文+标签)
"""
# 解析密钥
key = base64.b64decode(key_str)
# 生成随机IV
iv = get_random_bytes(AesGcmUtil.IV_LENGTH)
# 初始化GCM加密器
cipher = AES.new(key, AES.MODE_GCM, nonce=iv, mac_len=AesGcmUtil.TAG_LENGTH)
# 加密(GCM模式无需填充)
ciphertext, tag = cipher.encrypt_and_digest(plaintext.encode('utf-8'))
# 拼接IV+密文+标签
result = iv + ciphertext + tag
return base64.b64encode(result).decode('utf-8')
@staticmethod
def decrypt(ciphertext_str, key_str):
"""
GCM模式解密
:param ciphertext_str: Base64编码的密文
:param key_str: Base64编码的密钥
:return: 明文字符串
"""
# 解析密钥和密文
key = base64.b64decode(key_str)
ciphertext = base64.b64decode(ciphertext_str)
# 提取IV、密文、标签
iv = ciphertext[:AesGcmUtil.IV_LENGTH]
encrypted_data = ciphertext[AesGcmUtil.IV_LENGTH:-AesGcmUtil.TAG_LENGTH]
tag = ciphertext[-AesGcmUtil.TAG_LENGTH:]
# 初始化解密器
cipher = AES.new(key, AES.MODE_GCM, nonce=iv, mac_len=AesGcmUtil.TAG_LENGTH)
# 解密并验证标签
plaintext = cipher.decrypt_and_verify(encrypted_data, tag)
return plaintext.decode('utf-8')
# 测试
if __name__ == "__main__":
key = AesGcmUtil.generate_key()
plaintext = "AES-GCM加密测试:包含中文、特殊字符!@#$%";
print(f"密钥(Base64):{key}")
# 加密
ciphertext = AesGcmUtil.encrypt(plaintext, key)
print(f"加密结果:{ciphertext}")
# 解密
decrypted = AesGcmUtil.decrypt(ciphertext, key)
print(f"解密结果:{decrypted}")
四、CBC 模式实现
兼容老系统使用的 CBC 模式,需注意填充方式和 IV 管理
java
public class AesCbcUtil {
private static final int IV_LENGTH = 16; // CBC模式IV长度=块大小16字节
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding"; // 实际执行PKCS7填充
// 加密方法
public static String encryptCbc(String plaintext, String keyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
byte[] iv = new byte[IV_LENGTH];
new SecureRandom().nextBytes(iv);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
byte[] result = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(ciphertext, 0, result, iv.length, ciphertext.length);
return Base64.getEncoder().encodeToString(result);
}
}
五、注意事项
1. 密钥管理
-
不要硬编码密钥:通过配置中心(如 Nacos、Apollo)或密钥管理服务(如 AWS KMS、阿里云 KMS)存储;
-
密钥轮换:定期更换密钥(如每 3 个月),避免长期使用同一密钥;
-
密钥派生:若需从密码生成密钥,使用 PBKDF2 算法(如
PBKDF2WithHmacSHA256),迭代次数≥10000 次。
2. 加密参数选择
-
密钥:优先 128 位(性能与安全平衡),高安全场景用 256 位;
-
模式:新项目强制使用 GCM(AEAD 模式),避免使用 ECB/CBC;
-
IV/Nonce:必须随机且唯一,GCM 模式 IV 推荐 12 字节,不可重复使用同一 IV + 密钥组合;
-
编码:密文统一用 Base64 编码(便于存储传输),字符集统一 UTF-8。
3. 安全防护
-
防侧信道攻击:优先使用硬件加速实现(如 AES-NI、ARM Crypto Extensions);
-
防篡改:使用 GCM 模式自带的认证功能,避免手动拼接 MAC;
-
异常处理:解密时捕获
AEADBadTagException(数据篡改)、IllegalBlockSizeException(数据长度异常)等,不泄露敏感信息; -
禁用弱算法:禁止使用 AES-ECB、Zero Padding、短密钥(如 128 位以下)。
4. 跨平台兼容
-
统一参数:加密模式、填充方式、IV 长度、字符集必须一致;
-
密文格式:约定 IV、密文、标签的拼接顺序(如 IV 在前,标签在后);
-
测试验证:不同语言(Java/Python/Go)之间必须做互通测试,确保加密解密一致。
六、常见问题排查
- 解密失败:IllegalBlockSizeException
-
原因:密文长度不是 16 字节的整数倍、IV 长度错误、填充方式不匹配;
-
解决:检查加密解密的模式、填充、IV 长度是否一致。
- 解密失败:AEADBadTagException
-
原因:密文被篡改、密钥错误、IV 错误、标签长度不匹配;
-
解决:验证密钥和 IV 的正确性,检查密文传输过程中是否被修改。
- 跨平台解密失败
-
原因:字符集不一致(如 Java 用 GBK,Python 用 UTF-8)、密文格式拼接顺序不同;
-
解决:统一使用 UTF-8 字符集,明确 IV、密文、标签的拼接规则。
- 性能瓶颈
-
原因:使用软件实现的 AES-256、未启用硬件加速;
-
解决:改用 AES-128、启用硬件加速(如 Java 添加 JVM 参数
-XX:+UseAESNI)。