1.概述
对称密钥块密码在数据加密中起重要作用。这意味着同一密钥可用于加密和解密。高级加密标准(AES)是一种广泛使用的对称密钥加密算法。
在本教程中,我们将看到如何使用JDK中的Java密码体系结构(JCA)来实现AES加密和解密。
2. AES算法
AES算法是一种迭代的对称密钥块密码,它支持128、192和256位的加密密钥(秘密密钥),以对128位的块中的数据进行加密和解密。下图显示了高级AES算法:
如果要加密的数据不满足128位的块大小要求,则必须对其进行填充。填充是将最后一块填充为128位的过程。
3. AES变体
AES算法具有六种操作模式:
- ECB(电子密码本)
- CBC(密码块链接)
- CFB(密码反馈)
- OFB(输出反馈)
- 点击率(计数器)
- GCM(Galois /计数器模式)
可以应用操作模式以增强加密算法的效果。此外,操作模式可以将分组密码转换为流密码。每种模式都有其优势和劣势。让我们快速回顾一下。
3.1。ECB
这种操作模式是最简单的。明文分为大小为128位的块。然后,将使用相同的密钥和算法对每个块进行加密。因此,对于相同的块它会产生相同的结果。这是此模式的主要缺点,不建议用于加密。它需要填充数据。
3.2。CBC
为了克服ECB的弱点,CBC模式使用初始化矢量(IV)来增强加密。首先,CBC将明文块xor与IV一起使用。然后,它将结果加密到密文块。在下一个块中,它将使用加密结果与明文块进行异或,直到最后一个块。
在这种模式下,加密不能并行化,但解密可以并行化。它还需要填充数据。
3.3。 CFB
此模式可用作流密码。首先,它对IV进行加密,然后将其与纯文本块进行异或运算以获得密文。然后,CFB将加密结果加密以对明文进行异或。它需要IV。
在这种模式下,解密可以并行化,但加密不能并行化。
3.4。OFB
此模式可用作流密码。首先,它加密IV。然后,它使用加密结果对明文进行异或运算以获得密文。
它不需要填充数据,也不会受到噪声块的影响。
3.5。CTR
该模式将计数器的值用作IV。它与OFB非常相似,但是它每次使用计数器而不是IV对其进行加密。
此模式具有两个优点,包括加密/解密并行化,并且一个块中的噪声不会影响其他块。
3.6。 GCM
此模式是CTR模式的扩展。 GCM已受到NIST的高度重视并推荐使用。 GCM模型输出密文和认证标签。与该算法的其他运算模式相比,此模式的主要优点是效率高。
在本教程中,我们将使用AES/CBC/PKCS5Padding
算法,因为该算法已在许多项目中广泛使用。
3.7。加密后的数据大小
如前所述,AES的块大小为128位或16个字节。 AES不会更改大小,并且密文大小等于明文大小。另外,在ECB和CBC模式下,我们应该使用类似于PKCS 5.
的填充算法PKCS 5.
因此,加密后的数据大小为:
ciphertext_size (bytes) = cleartext_size + (16 - (cleartext_size % 16))
为了用密文存储IV,我们需要再增加16个字节。
4. AES参数
在AES算法中,我们需要三个参数:输入数据,秘密密钥和IV。 ECB模式下不使用IV。
4.1。输入数据
AES的输入数据可以是基于字符串,文件,对象和密码的。
4.2。密钥
在AES中生成密钥的方法有两种:从随机数生成或从给定密码生成。
在第一种方法中,应该从像SecureRandom
类这样的加密安全(伪)随机数生成器生成秘密密钥。
为了生成密钥,我们可以使用KeyGenerator
类。让我们定义一种用于生成大小为n
(128、192和256)位的AES密钥的方法:
public static SecretKey generateKey(int n) throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(n);
SecretKey key = keyGenerator.generateKey();
return key;
}
在第二种方法中,可以使用基于密码的密钥派生功能(例如PBKDF2)从给定的密码派生AES秘密密钥。我们还需要一个盐值来将密码转换为密钥。盐也是一个随机值。
我们可以将SecretKeyFactory
类与PBKDF2WithHmacSHA256
算法一起使用,以根据给定的密码生成密钥。
让我们定义一种方法,该方法可通过65,536次迭代和256位密钥长度从给定密码生成AES密钥:
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。初始化矢量(IV)
IV是伪随机值,其大小与加密的块相同。我们可以使用SecureRandom
类生成随机IV。
让我们定义一种生成IV的方法:
public static IvParameterSpec generateIv() {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
return new IvParameterSpec(iv);
}
5.加密和解密
5.1。串
要实现输入字符串加密,我们首先需要根据上一节生成密钥和IV。下一步,我们使用getInstance()
方法从Cipher
类创建一个实例。
此外,我们使用带有秘密密钥,IV和加密模式的init()
方法配置密码实例。最后,我们通过调用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);
}
为了解密输入字符串,我们可以使用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);
}
让我们编写一个用于加密和解密字符串输入的测试方法:
@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。文件
现在,让我们使用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();
}
请注意,不建议尝试将整个文件(尤其是大文件)读入内存。相反,我们一次加密一个缓冲区。
为了解密文件,我们使用类似的步骤,并使用DECRYPT_MODE
初始化密码, DECRYPT_MODE
。
再次,让我们定义一个用于加密和解密文本文件的测试方法。在这种方法中,我们从测试资源目录中读取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。基于密码
我们可以使用从给定密码派生的密钥进行AES加密和解密。
为了生成密钥,我们使用getKeyFromPassword()
方法。加密和解密步骤与字符串输入部分中显示的步骤相同。然后,我们可以使用实例化的密码和提供的密钥来执行加密。
让我们写一个测试方法:
@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。目的
为了加密Java对象,我们需要使用SealedObject
类。该对象应可Serializable
。让我们从定义Student
类开始:
public class Student implements Serializable {
private String name;
private int age;
// standard setters and getters
}
接下来,让我们加密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;
}
稍后可以使用正确的密码对加密对象进行解密:
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;
}
让我们写一个测试用例:
@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);
}
六,结论
总之,我们已经学习了如何使用Java中的AES算法来加密和解密输入数据,例如字符串,文件,对象和基于密码的数据。此外,我们讨论了AES的变化以及加密后数据的大小。
0 评论