Java 使用 AES 进行加密和解密

上一文 一文中,介绍了如何在 Java 中使用 RSA 非对称加密算法 进行加密、解密、生成数字签名和验签。

和 “非对称加密算法” 对应的就是 “对称加密算法”。非对称加密算法(如 RSA)的密钥通常由 公钥私钥 组成,且遵守公钥加密、私钥解密的模式。而对称加密算法则只有一个密钥,加密和解密都使用同一个密钥。

对称加密算法中,比较安全且流行的就是 AES 算法,本文将会带你了解如何在 Java 中使用 AES 对数据进行加密和解密。

AES 介绍

AES(Advanced Encryption Standard)是一种对称加密算法,也被称为高级加密标准。它是一种广泛使用的加密算法,具有高度的安全性和效率,已被广泛应用于各种领域,包括网络通信、数据存储和加密协议等。AES 使用相同的密钥进行加密和解密操作,因此被归类为对称加密算法。

密钥

可以使用 Java 中的 javax.crypto.KeyGenerator API 来生成随机的 AES 密钥。

package cn.springdoc.demo.test;

import java.security.SecureRandom;
import java.util.Base64;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

public class Main {

    public static void main(String[] args) throws Exception {

        // 获取 AES 密钥生成器
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");

        // 设置密钥长度和随机源
        keyGenerator.init(128, new SecureRandom());

        // 生成密钥
        SecretKey secretKey = keyGenerator.generateKey();

        // 获取密钥内容
        byte[] key = secretKey.getEncoded();

        System.out.println(Base64.getEncoder().encodeToString(key));
    }
}

如上,首先通过工厂方法获取到 AES 密钥生成器,然后设置密钥长度以及随机源,最后生成密钥。

执行 main 方法,输出的密钥如下(Base64编码):

wsWcvZhevZ2QzkL+f3x8dw==

AES 算法支持不同的密钥长度,包括 128 位、192 位和 256 位。密钥长度越长,理论上破解该密钥的难度就越大,因此安全性也更高。但也会导致加密和解密操作的计算成本增加。

加密和解密

在 Java 中使用 AES 进行加密、解密的操作示例如下:

package cn.springdoc.demo.test;

import java.security.SecureRandom;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

public class Main {

    /**
     * 加密
     * @param key		密钥
     * @param data		加密数据
     * @return			密文
     * @throws Exception
     */
    public static byte[] encode(byte[] key, byte[] data) throws Exception {
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        return cipher.doFinal(data);
    }

    /**
     * 解密
     * @param key		密钥
     * @param data		密文
     * @return			解密后的数据
     * @throws Exception
     */
    public static byte[] decode(byte[] key, byte[] data) throws Exception {
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
        return cipher.doFinal(data);
    }

    public static void main(String[] args) throws Exception {
        
        // 生成随机密钥
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(256, new SecureRandom());
        SecretKey secretKey = keyGenerator.generateKey();
        
        byte[] key = secretKey.getEncoded();
        System.out.println("AES 密钥:" + Base64.getEncoder().encodeToString(key));
        
        String content = "Hello springdoc.cn";
        System.out.println("原文:" + content);
        
        byte[] ret = encode(key, content.getBytes());
        System.out.println("加密后的密文:" + Base64.getEncoder().encodeToString(ret));
        
        byte[] raw = decode(key, ret);
        System.out.println("解密后的原文:" + new String(raw));
    }
}

首先,encode 方法用于加密,第一个参数就是 byte[] 形式的密钥,第二个参数就是要加密的数据,最后返回加密后的密文。

接着,decode 方法用于解密,第一个参数也是 byte[] 形式的密钥,第二个参数就是加密后的密文,最后返回的是解密后的原文。

你可以看到,encodedecode 方法中的内容基本上大致相同。首先通过构造函数指定密钥和算法创建 SecretKeySpec 实例,它是一个密钥规范对象,该对象包含了使用字节数组表示的密钥及其相关的算法信息。然后通过工厂方法获取到 Cipher 实例,它是加密和解密操作的引擎。Cipher 工厂方法中的参数 AES/ECB/PKCS5Padding,分别指定了加密标准、加密模式以及填充模式:

  • AES:高级加密标准(Advanced Encryption Standard),一种对称加密算法,用于加密和解密数据。
  • ECB:电子密码本(Electronic Codebook)模式是一种基本的加密模式,将待加密的数据分成固定大小的块,并使用相同的密钥对每个块进行独立的加密。
  • PKCS5Padding:PKCS #5填充模式是一种数据块填充方案,用于在加密过程中将数据块的长度扩展到特定的块大小。它通过在数据块的末尾添加额外的字节,使数据块长度符合指定的块大小要求。

最后,在 main 方法中,对加密、解密方法进行了测试。首先成一个 256 位的 AES KEY,然后定义了一个字符串作为要加密的数据。然后调用 encode 方法使用生成的 KEY 对数据进行加密,获得密文。最后调用 decode 使用同一密钥对密文进行解密,获得原文。

运行 main 方法,输出如下:

AES 密钥:vx3v5NCf0G5CDhQhvZIxjD0p+03gvvTzw4KFecsVmM8=
原文:Hello springdoc.cn
加密后的密文:o2hHuR+qNr5zGf9PG+EO/7gJK4C+ZG+LWsOYxur1+xg=
解密后的原文:Hello springdoc.cn

如你所见,成功地使用 AES 密钥进行了加密和解密。

使用自定义密钥

AES 密钥也可以自己定义,只要是合法的长度即可。

修改一下上述 main 方法,使用自定义的 AES 密钥:

byte[] key = "Hello springdoc.cn springdoc.cn!".getBytes();
System.out.println("AES 密钥:" + Base64.getEncoder().encodeToString(key));

String content = "Hello springdoc.cn";
System.out.println("原文:" + content);

byte[] ret = encode(key, content.getBytes());
System.out.println("加密后的密文:" + Base64.getEncoder().encodeToString(ret));

byte[] raw = decode(key, ret);
System.out.println("解密后的原文:" + new String(raw));

上述代码中定义的 AES 密钥为:Hello springdoc.cn springdoc.cn!,刚好 32 个 ASCII 字符,也就是 32 字节,即 128 位(32 x 8)。

运行 main,输出如下:

AES 密钥:SGVsbG8gc3ByaW5nZG9jLmNuIHNwcmluZ2RvYy5jbiE=
原文:Hello springdoc.cn
加密后的密文:eA7FjuNumExamoQj8bd0j91UCRBvkN0aBwVhHogRuc8=
解密后的原文:Hello springdoc.cn

同样,也没任何问题。