1. Overview
1.概述
The symmetric-key block cipher plays an important role in data encryption. It means that the same key is used for both encryption and decryption. The Advanced Encryption Standard (AES) is a widely used symmetric-key encryption algorithm.
对称密钥区块密码在数据加密中发挥着重要作用。它意味着加密和解密都使用同一个密钥。高级加密标准(AES)是一种广泛使用的对称密钥加密算法。
In this tutorial, we’ll learn how to implement AES encryption and decryption using the Java Cryptography Architecture (JCA) within the JDK.
在本教程中,我们将学习如何使用JDK中的Java Cryptography Architecture(JCA)实现AES加密和解密。
2. AES Algorithm
2.AES算法
The AES algorithm is an iterative, symmetric-key block cipher that supports cryptographic keys (secret keys) of 128, 192, and 256 bits to encrypt and decrypt data in blocks of 128 bits. The below figure shows the high-level AES algorithm:
AES算法是一种迭代式的对称密钥区块密码,支持128、192和256位的加密密钥(秘钥),以128位的区块来加密和解密数据。下图显示了高层的AES算法。
If the data to be encrypted doesn’t meet the block size requirement of 128 bits, it must be padded. Padding is the process of filling up the last block to 128 bits.
如果要加密的数据不符合128比特的块大小要求,就必须进行填充。填充是将最后一个块填充到128位的过程。
3. AES Variations
3.AES的变化
The AES algorithm has six modes of operation:
AES算法有六种操作模式。
- ECB (Electronic Code Book)
- CBC (Cipher Block Chaining)
- CFB (Cipher FeedBack)
- OFB (Output FeedBack)
- CTR (Counter)
- GCM (Galois/Counter Mode)
We can apply the mode of operation in order to strengthen the effect of the encryption algorithm. Moreover, the mode of operation may convert the block cipher into a stream cipher. Each mode has its strengths and weaknesses. Let’s quickly review each one.
我们可以应用操作模式,以加强加密算法的效果。此外,操作模式可以将块状密码转换为流密码。每种模式都有其优势和劣势。让我们快速回顾一下每一种。
3.1. ECB
3.1. 欧洲中央银行
This mode of operation is the simplest of all. The plaintext is divided into blocks with a size of 128 bits. Then each block is encrypted with the same key and algorithm. Therefore, it produces the same result for the same block. This is the main weakness of this mode, and it’s not recommended for encryption. It requires padding data.
这种操作模式是最简单的。明文被分成大小为128位的块。然后每个区块用相同的密钥和算法进行加密。因此,它对同一区块产生相同的结果。这是这种模式的主要弱点,不推荐用于加密。它需要填充数据。
3.2. CBC
3.2 CBC
In order to overcome the ECB weakness, CBC mode uses an Initialization Vector (IV) to augment the encryption. First, CBC uses the plaintext block xor with the IV. Then it encrypts the result to the ciphertext block. In the next block, it uses the encryption result to xor with the plaintext block until the last block.
为了克服ECB的弱点,CBC模式使用初始化向量(IV)来增加加密的内容。首先,CBC使用明文块与IV相乘。然后,它将结果加密到密码文本块。在下一个区块中,它使用加密结果与明文区块相乘,直到最后一个区块。
In this mode, encryption can’t be parallelized, but decryption can be parallelized. It also requires padding data.
在这种模式下,加密不能被并行化,但解密可以被并行化。它还需要填充数据。
3.3. CFB
3.3.CFB
This mode can be used as a stream cipher. First, it encrypts the IV, then it will xor with the plaintext block to get ciphertext. Then CFB encrypts the encryption result to xor the plaintext. It needs an IV.
这种模式可以作为流密码使用。首先,它对IV进行加密,然后将与明文块进行xor,得到密码文本。然后,CFB对加密结果进行加密,以与明文进行xor。它需要一个IV。
In this mode, decryption can be parallelized, but encryption can’t be parallelized.
在这种模式下,解密可以被并行化,但加密不能被并行化。
3.4. OFB
3.4.OFB
This mode can be used as a stream cipher. First, it encrypts the IV. Then it uses the encryption results to xor the plaintext to get ciphertext.
这种模式可以作为流密码使用。首先,它对IV进行加密。然后,它使用加密结果与明文相乘,得到密文。
It doesn’t require padding data, and won’t be affected by the noisy block.
它不需要填充数据,也不会受到噪声块的影响。
3.5. CTR
3.5 CTR
This mode uses the value of a counter as an IV. It’s very similar to OFB, but it uses the counter to be encrypted every time instead of the IV.
这种模式使用一个计数器的值作为IV。它与OFB非常相似,但它每次都使用计数器而不是IV进行加密。
This mode has two strengths, including encryption/decryption parallelization, and noise in one block does not affect other blocks.
这种模式有两个优点,包括加密/解密平行化,一个区块的噪音不会影响其他区块。
3.6. GCM
3.6.GCM
This mode is an extension of the CTR mode. The GCM has received significant attention and is recommended by NIST. The GCM model outputs ciphertext and an authentication tag. The main advantage of this mode, compared to other operation modes of the algorithm, is its efficiency.
这种模式是CTR模式的延伸。GCM受到了极大的关注,并被NIST推荐。GCM模式输出密码文本和一个认证标签。与该算法的其他操作模式相比,该模式的主要优势在于其效率。
In this tutorial, we’ll use the AES/CBC/PKCS5Padding algorithm because it’s widely used in many projects.
在本教程中,我们将使用AES/CBC/PKCS5Padding算法,因为它在许多项目中被广泛使用。
3.7. Size of Data After Encryption
3.7.加密后的数据大小
As mentioned earlier, the AES has a block size of 128 bits or 16 bytes. The AES doesn’t change the size, and the ciphertext size is equal to the cleartext size. Also, in ECB and CBC modes, we should use a padding algorithm like PKCS 5. So the size of data after encryption is:
如前所述,AES的块大小为128比特或16字节。AES不改变大小,密文大小等于明文大小。另外,在ECB和CBC模式下,我们应该使用像PKCS 5.这样的填充算法,所以加密后的数据大小为。
ciphertext_size (bytes) = cleartext_size + (16 - (cleartext_size % 16))
For storing IV with ciphertext, we need to add 16 more bytes.
对于存储IV和密码文本,我们需要再增加16个字节。
4. AES Parameters
4.AES参数
In the AES algorithm, we need three parameters: input data, secret key, and IV. IV is not used in ECB mode.
在AES算法中,我们需要三个参数:输入数据、秘钥和IV。在ECB模式中不使用IV。
4.1. Input Data
4.1.输入数据
The input data to the AES can be string, file, object, and password-based.
AES的输入数据可以是基于字符串、文件、对象和密码。
4.2. Secret Key
4.2.密匙
There are two ways for generating a secret key in the AES: generating from a random number, or deriving from a given password.
在AES中,有两种生成秘钥的方法:从随机数生成,或从给定的密码中得出。
In the first approach, the secret key should be generated from a Cryptographically Secure (Pseudo-)Random Number Generator like the SecureRandom class.
在第一种方法中,秘密密钥应该由像SecureRandom类这样的加密安全(伪)随机数发生器生成。
For generating a secret key, we can use the KeyGenerator class. Let’s define a method for generating the AES key with the size of n (128, 192, and 256) bits:
为了生成密匙,我们可以使用KeyGenerator类。让我们定义一个生成AES密钥的方法,其大小为n(128、192和256)位。
public static SecretKey generateKey(int n) throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(n);
SecretKey key = keyGenerator.generateKey();
return key;
}
In the second approach, the AES secret key can be derived from a given password using a password-based key derivation function like PBKDF2. We also need a salt value for turning a password into a secret key. The salt is also a random value.
在第二种方法中,可以使用基于密码的密钥推导函数(如PBKDF2)从给定的密码中推导出AES秘钥。我们还需要一个盐值来把密码变成秘钥。该盐值也是一个随机值。
We can use the SecretKeyFactory class with the PBKDF2WithHmacSHA256 algorithm for generating a key from a given password.
我们可以使用SecretKeyFactory类和PBKDF2WithHmacSHA256算法来从给定的密码生成一个密钥。
Let’s define a method for generating the AES key from a given password with 65,536 iterations and a key length of 256 bits:
让我们定义一个从给定密码生成AES密钥的方法,迭代65,536次,密钥长度为256比特。
public static SecretKey getKeyFromPassword(String password, String salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 65536, 256);
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec)
.getEncoded(), "AES");
return secret;
}
4.3. Initialization Vector (IV)
4.3.初始化向量(IV)
IV is a pseudo-random value and has the same size as the block that is encrypted. We can use the SecureRandom class to generate a random IV.
IV是一个伪随机值,其大小与被加密的块相同。我们可以使用SecureRandom类来生成一个随机IV。
Let’s define a method for generating an IV:
我们来定义一个生成IV的方法。
public static IvParameterSpec generateIv() {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
return new IvParameterSpec(iv);
}
5. Encryption and Decryption
5.加密和解密
5.1. String
5.1 字符串
To implement input string encryption, we first need to generate the secret key and IV according to the previous section. As the next step, we create an instance from the Cipher class by using the getInstance() method.
为了实现输入字符串加密,我们首先需要根据上一节的内容生成秘钥和IV。下一步,我们通过使用getInstance()方法从Cipher类创建一个实例。
Additionally, we configure a cipher instance using the init() method with a secret key, IV, and encryption mode. Finally, we encrypt the input string by invoking the doFinal() method. This method gets bytes of input and returns ciphertext in bytes:
此外,我们使用init()方法配置一个带有秘密密钥、IV和加密模式的密码实例。最后,我们通过调用doFinal()方法对输入字符串进行加密。该方法获得输入的字节数,并以字节为单位返回加密文本。
public static String encrypt(String algorithm, String input, SecretKey key,
IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] cipherText = cipher.doFinal(input.getBytes());
return Base64.getEncoder()
.encodeToString(cipherText);
}
For decrypting an input string, we can initialize our cipher using the DECRYPT_MODE to decrypt the content:
对于解密一个输入字符串,我们可以使用DECRYPT_MODE初始化我们的密码来解密内容。
public static String decrypt(String algorithm, String cipherText, SecretKey key,
IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] plainText = cipher.doFinal(Base64.getDecoder()
.decode(cipherText));
return new String(plainText);
}
Let’s write a test method for encrypting and decrypting a string input:
让我们写一个测试方法来加密和解密一个字符串输入。
@Test
void givenString_whenEncrypt_thenSuccess()
throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException,
BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException {
String input = "baeldung";
SecretKey key = AESUtil.generateKey(128);
IvParameterSpec ivParameterSpec = AESUtil.generateIv();
String algorithm = "AES/CBC/PKCS5Padding";
String cipherText = AESUtil.encrypt(algorithm, input, key, ivParameterSpec);
String plainText = AESUtil.decrypt(algorithm, cipherText, key, ivParameterSpec);
Assertions.assertEquals(input, plainText);
}
5.2. File
5.2 文件
Now let’s encrypt a file using the AES algorithm. The steps are the same, but we need some IO classes to work with the files. Let’s encrypt a text file:
现在让我们用AES算法来加密一个文件。步骤是一样的,但我们需要一些IO类来处理这些文件。让我们来加密一个文本文件。
public static void encryptFile(String algorithm, SecretKey key, IvParameterSpec iv,
File inputFile, File outputFile) throws IOException, NoSuchPaddingException,
NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
FileInputStream inputStream = new FileInputStream(inputFile);
FileOutputStream outputStream = new FileOutputStream(outputFile);
byte[] buffer = new byte[64];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
byte[] output = cipher.update(buffer, 0, bytesRead);
if (output != null) {
outputStream.write(output);
}
}
byte[] outputBytes = cipher.doFinal();
if (outputBytes != null) {
outputStream.write(outputBytes);
}
inputStream.close();
outputStream.close();
}
Please note that trying to read the entire file, particularly if it’s large, into memory is not recommended. Instead, we encrypt a buffer at a time.
请注意,不建议尝试将整个文件,尤其是大文件,读入内存。相反,我们每次都会加密一个缓冲区。
For decrypting a file, we use similar steps and initialize our cipher using DECRYPT_MODE as we saw before.
对于解密文件,我们使用类似的步骤,并使用我们之前看到的DECRYPT_MODE初始化我们的密码。
Again, let’s define a test method for encrypting and decrypting a text file. In this method, we read the baeldung.txt file from the test resource directory, encrypt it into a file called baeldung.encrypted, and then decrypt the file into a new file:
同样,让我们定义一个测试方法来加密和解密一个文本文件。在这个方法中,我们从测试资源目录中读取baeldung.txt文件,将其加密成一个名为baeldung.encrypted的文件,然后将该文件解密成一个新文件。
@Test
void givenFile_whenEncrypt_thenSuccess()
throws NoSuchAlgorithmException, IOException, IllegalBlockSizeException,
InvalidKeyException, BadPaddingException, InvalidAlgorithmParameterException,
NoSuchPaddingException {
SecretKey key = AESUtil.generateKey(128);
String algorithm = "AES/CBC/PKCS5Padding";
IvParameterSpec ivParameterSpec = AESUtil.generateIv();
Resource resource = new ClassPathResource("inputFile/baeldung.txt");
File inputFile = resource.getFile();
File encryptedFile = new File("classpath:baeldung.encrypted");
File decryptedFile = new File("document.decrypted");
AESUtil.encryptFile(algorithm, key, ivParameterSpec, inputFile, encryptedFile);
AESUtil.decryptFile(
algorithm, key, ivParameterSpec, encryptedFile, decryptedFile);
assertThat(inputFile).hasSameTextualContentAs(decryptedFile);
}
5.3. Password-Based
5.3.基于密码的
We can do the AES encryption and decryption using the secret key that is derived from a given password.
我们可以使用从给定密码得出的秘密密钥进行AES加密和解密。
For generating a secret key, we use the getKeyFromPassword() method. The encryption and decryption steps are the same as those shown in the string input section. We can then use the instantiated cipher and the provided secret key to perform the encryption.
为了生成一个秘密密钥,我们使用getKeyFromPassword()方法。加密和解密的步骤与字符串输入部分所示的步骤相同。然后我们可以使用实例化的密码和提供的秘密密钥来执行加密。
Let’s write a test method:
我们来写一个测试方法。
@Test
void givenPassword_whenEncrypt_thenSuccess()
throws InvalidKeySpecException, NoSuchAlgorithmException,
IllegalBlockSizeException, InvalidKeyException, BadPaddingException,
InvalidAlgorithmParameterException, NoSuchPaddingException {
String plainText = "www.baeldung.com";
String password = "baeldung";
String salt = "12345678";
IvParameterSpec ivParameterSpec = AESUtil.generateIv();
SecretKey key = AESUtil.getKeyFromPassword(password,salt);
String cipherText = AESUtil.encryptPasswordBased(plainText, key, ivParameterSpec);
String decryptedCipherText = AESUtil.decryptPasswordBased(
cipherText, key, ivParameterSpec);
Assertions.assertEquals(plainText, decryptedCipherText);
}
5.4. Object
5.4 对象
For encrypting a Java object, we need to use the SealedObject class. The object should be Serializable. Let’s begin by defining a Student class:
为了加密一个Java对象,我们需要使用SealedObject类。该对象应该是Serializable。让我们首先定义一个Student类。
public class Student implements Serializable {
private String name;
private int age;
// standard setters and getters
}
Next, let’s encrypt the Student object :
接下来,让我们对Student对象进行加密。
public static SealedObject encryptObject(String algorithm, Serializable object,
SecretKey key, IvParameterSpec iv) throws NoSuchPaddingException,
NoSuchAlgorithmException, InvalidAlgorithmParameterException,
InvalidKeyException, IOException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
SealedObject sealedObject = new SealedObject(object, cipher);
return sealedObject;
}
The encrypted object can later be decrypted using the correct cipher:
被加密的对象以后可以用正确的密码进行解密。
public static Serializable decryptObject(String algorithm, SealedObject sealedObject,
SecretKey key, IvParameterSpec iv) throws NoSuchPaddingException,
NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException,
ClassNotFoundException, BadPaddingException, IllegalBlockSizeException,
IOException {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
Serializable unsealObject = (Serializable) sealedObject.getObject(cipher);
return unsealObject;
}
Now let’s write a test case:
现在我们来写一个测试用例。
@Test
void givenObject_whenEncrypt_thenSuccess()
throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException,
InvalidAlgorithmParameterException, NoSuchPaddingException, IOException,
BadPaddingException, ClassNotFoundException {
Student student = new Student("Baeldung", 20);
SecretKey key = AESUtil.generateKey(128);
IvParameterSpec ivParameterSpec = AESUtil.generateIv();
String algorithm = "AES/CBC/PKCS5Padding";
SealedObject sealedObject = AESUtil.encryptObject(
algorithm, student, key, ivParameterSpec);
Student object = (Student) AESUtil.decryptObject(
algorithm, sealedObject, key, ivParameterSpec);
assertThat(student).isEqualToComparingFieldByField(object);
}
6. Conclusion
6.结语
In this article, we learned how to encrypt and decrypt input data like strings, files, objects, and password-based data using the AES algorithm in Java. Additionally, we discussed the AES variations and the size of data after encryption.
在这篇文章中,我们学习了如何在Java中使用AES算法对输入数据进行加密和解密,如字符串、文件、对象和基于密码的数据。此外,我们还讨论了AES的变化和加密后的数据大小。
As always, the full source code of the article is available over on GitHub.
一如既往,文章的完整源代码可在GitHub上获得。