拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 Java AES加密和解密

Java AES加密和解密

白鹭 - 2021-11-24 949 0 0

1.概述

对称密钥块密码在数据加密中起重要作用。这意味着同一密钥可用于加密和解密。高级加密标准(AES)是一种广泛使用的对称密钥加密算法。

在本教程中,我们将看到如何使用JDK中的Java密码体系结构(JCA)来实现AES加密和解密。

2. AES算法

AES算法是一种迭代的对称密钥块密码,它支持128、192和256位的加密密钥(秘密密钥),以对128位的块中的数据进行加密和解密。下图显示了高级AES算法:

高级AES算法

如果要加密的数据不满足128位的块大小要求,则必须对其进行填充。填充是将最后一块填充为128位的过程。

3. AES变体

AES算法具有六种操作模式:

  1. ECB(电子密码本)
  2. CBC(密码块链接)
  3. CFB(密码反馈)
  4. OFB(输出反馈)
  5. 点击率(计数器)
  6. 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 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *