AES 加密解密详解及示例

密码学中的高级加密标准(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);
    }
}

关键注意点

  1. IV(初始化向量)必须随机生成(不可硬编码),长度推荐 12 字节(GCM 模式);

  2. GCM 模式无需额外填充(NoPadding),算法内部自动处理数据长度;

  3. 密文格式采用 "IV + 密文 + 标签" 拼接,解密时需按顺序提取;

  4. 解密时会自动验证认证标签,数据被篡改会抛出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)之间必须做互通测试,确保加密解密一致。

六、常见问题排查

  1. 解密失败:IllegalBlockSizeException
  • 原因:密文长度不是 16 字节的整数倍、IV 长度错误、填充方式不匹配;

  • 解决:检查加密解密的模式、填充、IV 长度是否一致。

  1. 解密失败:AEADBadTagException
  • 原因:密文被篡改、密钥错误、IV 错误、标签长度不匹配;

  • 解决:验证密钥和 IV 的正确性,检查密文传输过程中是否被修改。

  1. 跨平台解密失败
  • 原因:字符集不一致(如 Java 用 GBK,Python 用 UTF-8)、密文格式拼接顺序不同;

  • 解决:统一使用 UTF-8 字符集,明确 IV、密文、标签的拼接规则。

  1. 性能瓶颈
  • 原因:使用软件实现的 AES-256、未启用硬件加速;

  • 解决:改用 AES-128、启用硬件加速(如 Java 添加 JVM 参数-XX:+UseAESNI)。

相关推荐
Hello eveybody2 小时前
介绍最大公因数和最小公约数(Python)
开发语言·python
weixin_580614002 小时前
golang如何实现时间格式化_golang时间格式化方法详解
jvm·数据库·python
forEverPlume2 小时前
c++怎么利用std--span实现在不拷贝数据的前提下解析大规模文件【进阶】
jvm·数据库·python
Ulyanov2 小时前
《PySide6 GUI开发指南:QML核心与实践》 第十篇:综合实战——构建完整的跨平台个人管理应用
开发语言·python·qt·ui·交互·qml·雷达电子战系统仿真
aq55356002 小时前
数字资源分发的技术革命与未来趋势
java·开发语言·python·php
AI玫瑰助手2 小时前
Python基础:元组的定义与不可变特性(对比列表)
开发语言·python·信息可视化
张驰咨询公司2 小时前
六西格玛数据分析实战:用Python实现DPMO与西格玛水平计算
开发语言·python·数据分析·六西格玛培训·六西格玛培训公司
HHHHH1010HHHHH2 小时前
Tailwind CSS如何快速定义固定宽高比_使用aspect-square实现CSS正方形
jvm·数据库·python
雕刻刀2 小时前
linux中复制conda环境
linux·python·conda