Cryptage et décryptage Java AES

Haut Java

Je viens d'annoncer le nouveau cours Learn Spring , axé sur les principes de base de Spring 5 et Spring Boot 2:

>> VOIR LE COURS

1. Vue d'ensemble

Le chiffrement par bloc à clé symétrique joue un rôle important dans le chiffrement des données. Cela signifie que la même clé est utilisée pour le cryptage et le décryptage. Advanced Encryption Standard (AES) est un algorithme de chiffrement à clé symétrique largement utilisé.

Dans ce didacticiel, nous verrons comment implémenter le chiffrement et le déchiffrement AES à l'aide de l'architecture de cryptographie Java (JCA) dans le JDK.

2. Algorithme AES

L'algorithme AES est un chiffrement itératif par blocs à clé symétrique qui prend en charge les clés cryptographiques (clés secrètes) de 128, 192 et 256 bits pour crypter et décrypter les données par blocs de 128 bits . La figure ci-dessous montre l'algorithme AES de haut niveau:

Si les données à chiffrer ne répondent pas à l'exigence de taille de bloc de 128 bits, elles doivent être complétées. Le remplissage est un processus de remplissage du dernier bloc à 128 bits.

3. Variations AES

L'algorithme AES a six modes de fonctionnement:

  1. ECB (livre de codes électroniques)
  2. CBC (chaînage de blocs de chiffrement)
  3. CFB (Cipher FeedBack)
  4. OFB (Sortie FeedBack)
  5. CTR (compteur)
  6. GCM (mode Galois / compteur)

Le mode de fonctionnement peut être appliqué afin de renforcer l'effet de l'algorithme de cryptage. De plus, le mode de fonctionnement peut convertir le chiffrement par bloc en un chiffrement de flux. Chaque mode a sa force et sa faiblesse. Passons en revue rapidement.

3.1. BCE

Ce mode de fonctionnement est le plus simple de tous. Le texte brut est divisé en blocs d'une taille de 128 bits. Ensuite, chaque bloc sera chiffré avec la même clé et le même algorithme. Par conséquent, il produit le même résultat pour le même bloc. C'est la principale faiblesse de ce mode et il n'est pas recommandé pour le chiffrement . Il nécessite des données de remplissage.

3.2. CBC

Afin de surmonter la faiblesse de la BCE, le mode CBC utilise un vecteur d'initialisation (IV) pour augmenter le cryptage. Premièrement, CBC utilise le bloc en clair xor avec IV. Ensuite, il crypte le résultat dans le bloc de texte chiffré. Dans le bloc suivant, il utilise le résultat du chiffrement pour xor avec le bloc en clair jusqu'au dernier bloc.

Dans ce mode, le chiffrement ne peut pas être parallélisé, mais le déchiffrement peut être parallélisé. Il nécessite également des données de remplissage.

3.3. BFC

Ce mode peut être utilisé comme chiffrement de flux. Premièrement, il crypte le IV, puis il xor avec le bloc de texte en clair pour obtenir le texte chiffré. Ensuite, CFB crypte le résultat du chiffrement pour xou le texte en clair. Il a besoin d'un IV.

Dans ce mode, le déchiffrement peut être parallélisé mais le chiffrement ne peut pas être parallélisé.

3.4. OFB

Ce mode peut être utilisé comme chiffrement de flux. Premièrement, il crypte le IV. Ensuite, il utilise les résultats du chiffrement pour xor le texte brut pour obtenir le texte chiffré.

Il ne nécessite pas de données de remplissage et ne sera pas affecté par le bloc bruyant.

3.5. CTR

Ce mode utilise la valeur d'un compteur comme un IV. C'est très similaire à l'OFB, mais il utilise le compteur pour être chiffré à chaque fois au lieu de l'IV.

Ce mode a deux points forts, y compris la parallélisation de cryptage / décryptage, et le bruit dans un bloc n'affecte pas les autres blocs.

3.6. GCM

Ce mode est une extension du mode CTR. Le GCM a reçu une attention particulière et est recommandé par le NIST. Le modèle GCM génère un texte chiffré et une étiquette d'authentification. Le principal avantage de ce mode, par rapport aux autres modes de fonctionnement de l'algorithme, est son efficacité.

Dans ce didacticiel, nous utiliserons l' algorithme AES / CBC / PKCS5Padding car il est largement utilisé dans de nombreux projets.

3.7. Taille des données après le cryptage

Comme mentionné précédemment, l'AES a une taille de bloc de 128 bits ou 16 octets. L'AES ne modifie pas la taille et la taille du texte chiffré est égale à la taille du texte clair. De plus, dans les modes ECB et CBC, nous devrions utiliser un algorithme de remplissage comme PKCS 5. Ainsi, la taille des données après cryptage est:

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

Pour stocker IV avec texte chiffré, nous devons ajouter 16 octets supplémentaires.

4. Paramètres AES

Dans l'algorithme AES, nous avons besoin de trois paramètres: les données d'entrée, la clé secrète et IV. IV n'est pas utilisé en mode ECB.

4.1. Des données d'entrée

Les données d'entrée dans l'AES peuvent être basées sur des chaînes, des fichiers, des objets et des mots de passe.

4.2. Clef secrète

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

En résumé, nous avons appris à crypter et décrypter des données d'entrée telles que des chaînes, des fichiers, des objets et des données basées sur des mots de passe, à l'aide de l'algorithme AES en Java. De plus, nous avons discuté des variations AES et de la taille des données après le chiffrement.

Comme toujours, le code source complet de l'article est disponible à l'adresse over sur GitHub.

Fond Java

Je viens d'annoncer le nouveau cours Learn Spring , axé sur les principes de base de Spring 5 et Spring Boot 2:

>> VOIR LE COURS