Java中AES加解密

Java SDK 对 AES 的实现

AES 是 Advanced Encryption Standard 的缩写,也就是 高级加密标准 。具体可以见:高级加密标准。本文主要讨论使用 Java SDK中对 AES 的实现。

JDK 的 Cipher 文档 中,知道它支持 4 种 AES 加密模式:

  • AES/CBC/NoPadding (128)
  • AES/CBC/PKCS5Padding (128)
  • AES/ECB/NoPadding (128)
  • AES/ECB/PKCS5Padding (128)

AES 是一种加解密算法,那么 CBC, ECB, NoPadding 和 PKCS5Padding 是什么呢?

CBC, CBC 是分组密码工作模式,是对于按块处理密码的加密方式的一种扩充。NoPadding, PKCS5Padding 是填充(Padding),是对需要按块处理的数据,当数据长度不符合块处理需求时,按照一定方法填充满块长的一种规则。

关于分组密码工作模式,可以参考: 分组密码工作模式AES五种加密模式(CBC、ECB、CTR、OCF、CFB)
关于填充,可以参考:Padding (cryptography)

参考,JAVA AES算法 知道(以下部分的内容为该文章里的内容):

  • (1)缺省模式和填充为“AES/ECB/PKCS5Padding”,Cipher.getInstance(“AES”)与Cipher.getInstance(“AES/ECB/PKCS5Padding”)等效。
  • (2)JDK的PKCS5Padding实际是上述的PKCS7的实现。
  • (3)由于AES是按照16Byte为块进行处理,对于NoPadding而言,如果需要加密的原文长度不是16Byte的倍数,将无法处理抛出异常,其实是由用户自己选择Padding的算法。密文则必然是16Byte的倍数,否则密文肯定异常。
  • (4)如果加密为PKCS5Padding,解密可以选择NoPadding,也能解密成功,内容为原文加上PKCS5Padding之后的结果。
  • (5)如果原文最后一个字符为>=0x00&&<=0x10的内容,PKCS5Padding的解密将会出现异常,要么是符合PKCS5Padding,最后的内容被删除,要么不符合,则解密失败抛出异常。对此有两种思路,一是原文通过Base64编码为可见字符,二是原文自带长度使用NoPadding解密。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
public class AesUtil {
/**
* AES/CBC/PKCS5Padding加密,然后进行base64加密
*
* @param plainText
* @param key
* @return
* @throws Exception
*/
public static String encryptBase64AESCBC(String plainText, String key) throws Exception {
byte[] clean = plainText.getBytes();
//Cipher
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// Generating IV.
int ivSize = cipher.getBlockSize();
byte[] iv = new byte[ivSize];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
// Encrypt.
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] encrypted = cipher.doFinal(clean);
// Combine IV and encrypted part.
byte[] encryptedIVAndText = new byte[ivSize + encrypted.length];
System.arraycopy(iv, 0, encryptedIVAndText, 0, ivSize);
System.arraycopy(encrypted, 0, encryptedIVAndText, ivSize, encrypted.length);
return Base64.getEncoder().encodeToString(encryptedIVAndText);
}
/**
* base64解密后再进行AES/CBC/PKCS5Padding解密
*
* @param encryptedIvText
* @param key
* @return
* @throws Exception
*/
public static String decryptBase64AESCBC(String encryptedIvText, String key) throws Exception {
byte[] encryptedIvTextBytes = Base64.getDecoder().decode(encryptedIvText);
//Cipher
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// Extract IV.
int ivSize = cipher.getBlockSize();
byte[] iv = new byte[ivSize];
System.arraycopy(encryptedIvTextBytes, 0, iv, 0, iv.length);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
// Extract encrypted part.
int encryptedSize = encryptedIvTextBytes.length - ivSize;
byte[] encryptedBytes = new byte[encryptedSize];
System.arraycopy(encryptedIvTextBytes, ivSize, encryptedBytes, 0, encryptedSize);
// Decrypt.
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] original = cipher.doFinal(encryptedBytes);
String originalString = new String(original);
return originalString;
}
}

以上代码中 iv的长度为 cipher.getBlockSize(),然后将 iv 放在加密文本的前部分。解密的时候一样先获得 iv,再进行加密内容的解密。