Java AES криптиране и декриптиране

Java Top

Току що обявих новия курс Learn Spring , фокусиран върху основите на Spring 5 и Spring Boot 2:

>> ПРЕГЛЕД НА КУРСА

1. Общ преглед

Блок-шифърът със симетричен ключ играе важна роля в криптирането на данни. Това означава, че един и същ ключ се използва както за криптиране, така и за декриптиране. Стандартът за усъвършенствано шифроване (AES) е широко използван алгоритъм за криптиране със симетричен ключ.

В този урок ще видим как да приложим AES криптиране и дешифриране с помощта на Java Cryptography Architecture (JCA) в JDK.

2. AES алгоритъм

Алгоритъмът AES е итеративен блоков шифър със симетричен ключ, който поддържа криптографски ключове (секретни ключове) от 128, 192 и 256 бита за криптиране и декриптиране на данни в блокове от 128 бита . Фигурата по-долу показва алгоритъма AES на високо ниво:

Ако данните за криптиране не отговарят на изискването за размер на блока от 128 бита, те трябва да бъдат подплатени. Попълването е процес на запълване на последния блок до 128 бита.

3. AES вариации

Алгоритъмът AES има шест режима на работа:

  1. ЕЦБ (Електронна книга с кодове)
  2. CBC (верига на шифровани блокове)
  3. CFB (обратна връзка за шифър)
  4. OFB (изходна обратна връзка)
  5. CTR (брояч)
  6. GCM (режим Galois / брояч)

Режимът на работа може да се приложи, за да се засили ефектът от алгоритъма за криптиране. Освен това режимът на работа може да преобразува блоковия шифър в поточен шифър. Всеки режим има своята сила и слабост. Нека направим бърз преглед.

3.1. ЕЦБ

Този режим на работа е най-простият от всички. Откритият текст е разделен на блокове с размер 128 бита. Тогава всеки блок ще бъде криптиран със същия ключ и алгоритъм. Следователно той дава същия резултат за същия блок. Това е основната слабост на този режим и не се препоръчва за криптиране . Изисква данни за подпълване.

3.2. CBC

За да се преодолее слабостта на ЕЦБ, режимът CBC използва вектор за инициализация (IV), за да увеличи криптирането. Първо, CBC използва блока за открит текст xor с IV. След това криптира резултата в блока на шифротекста. В следващия блок той използва резултата от криптирането, за да xor с блока на обикновения текст до последния блок.

В този режим криптирането не може да бъде паралелизирано, но декриптирането може да бъде паралелизирано. Също така се изискват данни за запълване.

3.3. CFB

Този режим може да се използва като поточен шифър. Първо, той криптира IV, след това ще се xor с блока на обикновения текст, за да получи шифротекст. След това CFB криптира резултата от криптирането, за да xor или обикновения текст. Нуждае се от IV.

В този режим дешифрирането може да бъде паралелизирано, но криптирането не може да бъде паралелизирано.

3.4. OFB

Този режим може да се използва като поточен шифър. Първо, той криптира IV. След това използва резултатите от криптирането, за да xor или обикновения текст, за да получи шифротекст.

Не изисква данни за подпълване и няма да бъде засегнат от шумния блок.

3.5. CTR

Този режим използва стойността на брояч като IV. Той е много подобен на OFB, но използва брояча, за да бъде криптиран всеки път вместо IV.

Този режим има две силни страни, включително паралелизиране на криптиране / декриптиране, а шумът в един блок не засяга други блокове.

3.6. GCM

Този режим е продължение на режима на CTR. GCM получи значително внимание и се препоръчва от NIST. Моделът GCM извежда шифротекст и таг за удостоверяване. Основното предимство на този режим, в сравнение с други режими на работа на алгоритъма, е неговата ефективност.

В този урок ще използваме алгоритъма AES / CBC / PKCS5Padding , защото той се използва широко в много проекти.

3.7. Размер на данните след шифроване

Както бе споменато по-рано, AES има размер на блока от 128 бита или 16 байта. AES не променя размера и размерът на шифъртекста е равен на размера на чистия текст. Също така, в режимите на ЕЦБ и CBC, трябва да използваме алгоритъм за подплата като PKCS 5. И така, размерът на данните след криптирането е:

ciphertext_size (bytes) = cleartext_size + (16 - (cleartext_size % 16))

За съхраняване на IV с шифротекст трябва да добавим още 16 байта.

4. AES параметри

В алгоритъма AES се нуждаем от три параметъра: входни данни, секретен ключ и IV. IV не се използва в режим на ЕЦБ.

4.1. Входни данни

Входните данни към AES могат да бъдат базирани на низ, файл, обект и парола.

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.

In the first approach, the secret key should be generated from a Cryptographically Secure (Pseudo-)Random Number Generator like the SecureRandom class.

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:

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.

We can use the SecretKeyFactory class with the PBKDF2WithHmacSHA256 algorithm for generating a key from a given password.

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:

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)

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.

Let’s define a method for generating an IV:

public static IvParameterSpec generateIv() { byte[] iv = new byte[16]; new SecureRandom().nextBytes(iv); return new IvParameterSpec(iv); }

5. Encryption and Decryption

5.1. String

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.

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:

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:

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

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:

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 is 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.

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:

@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

We can do the AES encryption and decryption using the secret key that is derived from a given password.

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.

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

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:

public class Student implements Serializable { private String name; private int age; // standard setters and getters } 

Next, let's encrypt the Student object :

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; }

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

В обобщение научихме как да шифроваме и декриптираме входни данни като низове, файлове, обекти и базирани на парола данни, използвайки алгоритъма AES в Java. Освен това обсъдихме вариациите на AES и размера на данните след криптиране.

Както винаги, пълният изходен код на статията е достъпен в GitHub.

Дъно на Java

Току що обявих новия курс Learn Spring , фокусиран върху основите на Spring 5 и Spring Boot 2:

>> ПРЕГЛЕД НА КУРСА