杰瑞科技汇

Java AES加密解密如何实现?

AES(Advanced Encryption Standard)是一种对称加密算法,意味着加密和解密使用的是同一个密钥,它速度快,安全性高,是目前最广泛使用的对称加密算法之一。

Java AES加密解密如何实现?-图1
(图片来源网络,侵删)

在 Java 中,我们通常使用 javax.crypto 包中的类来实现 AES 加密解密,下面我将提供一个完整、可运行的示例,并逐步解释其中的关键点。


完整代码示例

这是一个可以直接运行的 Java 类,包含了 AES 的加密和解密方法。

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
public class AESUtils {
    // AES 算法名称
    private static final String AES_ALGORITHM = "AES";
    // 加密模式 / 填充方式
    // 推荐使用 CBC 或 GCM 模式,这里以 CBC 为例
    private static final String AES_TRANSFORMATION = "AES/CBC/PKCS5Padding";
    // IV 的长度,CBC 模式下必须为 16 字节
    private static final int IV_LENGTH = 16;
    /**
     * 生成 AES 密钥
     * @param keySize 密钥长度(128, 192, 256)
     * @return Base64 编码的密钥字符串
     * @throws Exception
     */
    public static String generateKey(int keySize) throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(AES_ALGORITHM);
        keyGenerator.init(keySize);
        SecretKey secretKey = keyGenerator.generateKey();
        return Base64.getEncoder().encodeToString(secretKey.getEncoded());
    }
    /**
     * 加密
     * @param plaintext  明文
     * @param keyStr     Base64 编码的密钥字符串
     * @return Base64 编码的密文 + IV 的字符串 (格式: "iv:base64 ciphertext")
     * @throws Exception
     */
    public static String encrypt(String plaintext, String keyStr) throws Exception {
        // 1. 准备密钥
        byte[] keyBytes = Base64.getDecoder().decode(keyStr);
        SecretKeySpec secretKey = new SecretKeySpec(keyBytes, AES_ALGORITHM);
        // 2. 生成随机 IV
        byte[] iv = new byte[IV_LENGTH];
        new SecureRandom().nextBytes(iv);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        // 3. 初始化 Cipher
        Cipher cipher = Cipher.getInstance(AES_TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
        // 4. 执行加密
        byte[] encryptedBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
        // 5. 组合 IV 和密文,并返回 Base64 编码后的字符串
        // IV 不需要保密,但必须与密文一起传输或存储,用于解密
        byte[] combined = new byte[iv.length + encryptedBytes.length];
        System.arraycopy(iv, 0, combined, 0, iv.length);
        System.arraycopy(encryptedBytes, 0, combined, iv.length, encryptedBytes.length);
        return "iv:" + Base64.getEncoder().encodeToString(combined);
    }
    /**
     * 解密
     * @param encryptedStr 加密后的字符串 (格式: "iv:base64 ciphertext")
     * @param keyStr       Base64 编码的密钥字符串
     * @return 明文字符串
     * @throws Exception
     */
    public static String decrypt(String encryptedStr, String keyStr) throws Exception {
        // 1. 准备密钥
        byte[] keyBytes = Base64.getDecoder().decode(keyStr);
        SecretKeySpec secretKey = new SecretKeySpec(keyBytes, AES_ALGORITHM);
        // 2. 从加密字符串中提取 IV 和密文
        if (!encryptedStr.startsWith("iv:")) {
            throw new IllegalArgumentException("Invalid encrypted string format.");
        }
        String combinedBase64 = encryptedStr.substring(3);
        byte[] combined = Base64.getDecoder().decode(combinedBase64);
        byte[] iv = new byte[IV_LENGTH];
        byte[] encryptedBytes = new byte[combined.length - IV_LENGTH];
        System.arraycopy(combined, 0, iv, 0, iv.length);
        System.arraycopy(combined, iv.length, encryptedBytes, 0, encryptedBytes.length);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        // 3. 初始化 Cipher
        Cipher cipher = Cipher.getInstance(AES_TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
        // 4. 执行解密
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }
    public static void main(String[] args) {
        try {
            // --- 步骤 1: 生成密钥 ---
            // AES 支持 128, 192, 256 位密钥,Java 默认限制为 128 位,如需 256 位,需安装 JCE 无限强度策略文件。
            String key = generateKey(256);
            System.out.println("生成的 AES 密钥 (Base64): " + key);
            // --- 步骤 2: 准备明文 ---
            String originalText = "这是一段需要加密的敏感信息 Hello, AES!";
            System.out.println("原始明文: " + originalText);
            // --- 步骤 3: 加密 ---
            String encryptedText = encrypt(originalText, key);
            System.out.println("加密后的字符串: " + encryptedText);
            // --- 步骤 4: 解密 ---
            String decryptedText = decrypt(encryptedText, key);
            System.out.println("解密后的明文: " + decryptedText);
            // --- 验证 ---
            if (originalText.equals(decryptedText)) {
                System.out.println("解密成功,与原始明文一致!");
            } else {
                System.out.println("解密失败,明文不匹配!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

核心概念解析

1 javax.crypto 包中的核心类

  • KeyGenerator: 用于生成对称密钥,我们可以用它来生成 AES 密钥。
  • SecretKey: 表示一个秘密密钥。KeyGenerator.generateKey() 返回的就是这个对象,我们通常将其编码为字节数组或 Base64 字符串以便存储和传输。
  • SecretKeySpec: SecretKey 的一个具体实现,它允许我们从一个字节数组来构造一个密钥,这在当我们从存储中读取密钥时非常有用。
  • Cipher: 这是执行加密和解密的核心引擎,我们用它来初始化模式(加密/解密)并处理数据。
  • IvParameterSpec: 初始化向量(IV)的规范,IV 是一个随机数,用于增加加密的安全性,防止相同的明文总是产生相同的密文(这被称为“确定性加密”)。

2 加密模式与填充方式 (AES_TRANSFORMATION)

Cipher.getInstance("AES/CBC/PKCS5Padding") 这行代码指定了加密的三个要素:

  1. 算法: AES
  2. 模式: CBC (Cipher Block Chaining, 密码块链接)。
    • ECB (Electronic Codebook): 不推荐使用,它会独立地加密每个数据块,相同的明文块会产生相同的密文块,不安全。
    • CBC (Cipher Block Chain): 推荐使用,每个明文块在加密前会与前一个密文块进行异或(XOR)操作,并且需要一个随机的 IV 来加密第一个块,这使得相同的明文在不同的 IV 下会产生不同的密文。
    • GCM (Galois/Counter Mode): 现代且更安全的模式,它不仅提供加密,还提供认证,能检测密文是否被篡改,如果你需要更高的安全性,可以考虑使用 AES/GCM/NoPadding
  3. 填充方式: PKCS5Padding
    • AES 算法要求数据长度必须是 16 字节(128位)的整数倍,如果原始数据长度不够,就需要进行填充。PKCS5Padding 是一种常用的填充方案,它会填充缺少的字节数,并在每个填充字节中填入这个字节数。

3 初始化向量

  • 作用: IV 是 CBC 模式下的“盐”,它确保即使加密的是完全相同的数据,每次生成的密文也不同,这是防止“彩虹表”攻击和模式分析的关键。
  • 生成: IV 必须是随机且不可预测的,通常使用 SecureRandom 来生成。
  • 传输/存储: IV 不需要保密,但它必须与密文一起发送给接收方,或者与密文一起存储,如果没有正确的 IV,解密将无法进行,在我们的示例中,我们将 IV 和密文拼接在一起,并用 Base64 编码,方便传输和存储。

4 密钥长度

AES 支持 128、192 和 256 位三种密钥长度。

  • 在 Java 中,出于历史原因,默认的策略限制密钥长度为 128 位。
  • 如果要使用 192 或 256 位密钥,你需要下载并安装 Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files,否则,KeyGenerator.init(256) 会抛出 InvalidKeyException
  • 对于大多数应用场景,256 位密钥提供了极高的安全性,是推荐的选择。

使用 GCM 模式的更安全实现

GCM (Galois/Counter Mode) 是比 CBC 更现代的模式,它结合了加密和消息认证码,能够有效防止数据篡改,使用 GCM 时,IV 的长度通常为 12 字节(96位),这是推荐值。

下面是使用 GCM 模式的代码:

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
public class AESGCMUtils {
    private static final String AES_ALGORITHM = "AES";
    private static final String AES_GCM_TRANSFORMATION = "AES/GCM/NoPadding";
    // GCM 推荐使用 12 字节的 IV
    private static final int GCM_IV_LENGTH = 12;
    // GCM 认证标签长度,通常为 128 位 (16 字节)
    private static final int GCM_TAG_LENGTH = 128;
    /**
     * 使用 GCM 模式加密
     */
    public static String encryptGCM(String plaintext, String keyStr) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(keyStr);
        SecretKeySpec secretKey = new SecretKeySpec(keyBytes, AES_ALGORITHM);
        byte[] iv = new byte[GCM_IV_LENGTH];
        new SecureRandom().nextBytes(iv);
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
        Cipher cipher = Cipher.getInstance(AES_GCM_TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec);
        byte[] encryptedBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
        // IV + 密文
        byte[] combined = new byte[iv.length + encryptedBytes.length];
        System.arraycopy(iv, 0, combined, 0, iv.length);
        System.arraycopy(encryptedBytes, 0, combined, iv.length, encryptedBytes.length);
        return "gcm:" + Base64.getEncoder().encodeToString(combined);
    }
    /**
     * 使用 GCM 模式解密
     */
    public static String decryptGCM(String encryptedStr, String keyStr) throws Exception {
        if (!encryptedStr.startsWith("gcm:")) {
            throw new IllegalArgumentException("Invalid GCM encrypted string format.");
        }
        String combinedBase64 = encryptedStr.substring(4);
        byte[] combined = Base64.getDecoder().decode(combinedBase64);
        byte[] keyBytes = Base64.getDecoder().decode(keyStr);
        SecretKeySpec secretKey = new SecretKeySpec(keyBytes, AES_ALGORITHM);
        byte[] iv = new byte[GCM_IV_LENGTH];
        byte[] encryptedBytes = new byte[combined.length - GCM_IV_LENGTH];
        System.arraycopy(combined, 0, iv, 0, iv.length);
        System.arraycopy(combined, iv.length, encryptedBytes, 0, encryptedBytes.length);
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
        Cipher cipher = Cipher.getInstance(AES_GCM_TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec);
        // 如果密文被篡改,doFinal 会抛出 AEADBadTagException
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }
    // main 方法可以复用上面的,只需调用 encryptGCM 和 decryptGCM
}
特性 CBC 模式 GCM 模式
安全性 良好,但易受到填充 oracle 攻击(如果实现不当) 优秀,提供加密和认证,防篡改
性能 较慢,需要串行处理数据块 更快,可以并行处理数据块
IV 必须是 16 字节随机数 推荐 12 字节随机数
额外输出 生成一个认证标签(密文的一部分)
推荐度 仍然可用,但 GCM 是更好的选择 强烈推荐用于新的项目

在实际开发中,优先选择 AES/GCM 模式,因为它提供了更高级别的安全性和更好的性能,无论使用哪种模式,IV 的正确生成和管理都是至关重要的。

分享:
扫描分享到社交APP
上一篇
下一篇