Chiffrement et déchiffrement Java AES

image de l'auteur
Par mkyong | Dernière mise à jour : 2 juin 2020
Vu : 64 491 | +1 553 pv/w

Cryptage AES

L’Advanced Encryption Standard (AES, Rijndael) est un algorithme de cryptage et de décryptage par blocs, l’algorithme de cryptage le plus utilisé dans le monde. L’AES traite des blocs de 128 bits en utilisant une clé secrète de 128, 192 ou 256 bits.

Cet article vous montre quelques exemples de cryptage et de décryptage Java AES :

  • Cryptage de chaîne de caractères AES – (crypte et décrypte une chaîne de caractères).
  • Cryptage basé sur un mot de passe AES – (La clé secrète dérivera d’un mot de passe donné).
  • Cryptage de fichier AES. (basé sur un mot de passe).

Dans cet article, nous nous concentrons sur le chiffrement AES 256 bits avec le mode compteur de Galois (GCM).

GCM = CTR + Authentication.

Lecture complémentaire
Lisez ceci – NIST – Recommandation pour le mode Galois/compteur (GCM)

Ne pas utiliser le mode AES Electronic codebook (ECB)
Le mode AES ECB, ou AES/ECB/PKCS5Padding (en Java) n’est pas sémantiquement sûr – Le texte chiffré en ECB peut laisser échapper des informations sur le texte en clair. Voici une discussion sur Pourquoi ne devrais-je pas utiliser le chiffrement ECB ?

Java et les entrées de chiffrement AES.

Dans le chiffrement et le déchiffrement AES, nous avons besoin des entrées suivantes :

Bonne pratique de chiffrement AES
Ne pas réutiliser IV avec la même clé.

1.1 L’IV (valeur initiale ou vecteur initial), il s’agit d’octets aléatoires, typiquement 12 octets ou 16 octets. En Java, nous pouvons utiliser SecureRandom pour générer le IV aléatoire.

 // 16 bytes IV public static byte getRandomNonce() { byte nonce = new byte; new SecureRandom().nextBytes(nonce); return nonce; } // 12 bytes IV public static byte getRandomNonce() { byte nonce = new byte; new SecureRandom().nextBytes(nonce); return nonce; }

1.2 La clé secrète AES, soit AES-128 ou AES-256. En Java, nous pouvons utiliser KeyGenerator pour générer la clé secrète AES.

 // 256 bits AES secret key public static SecretKey getAESKey() throws NoSuchAlgorithmException { KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(256, SecureRandom.getInstanceStrong()); return keyGen.generateKey(); }

1.3 La clé secrète AES qui est dérivée d’un mot de passe donné. En Java, nous pouvons utiliser les SecretKeyFactory et PBKDF2WithHmacSHA256 pour générer une clé AES à partir d’un mot de passe donné.

 // AES key derived from a password public static SecretKey getAESKeyFromPassword(char password, byte salt) throws NoSuchAlgorithmException, InvalidKeySpecException { SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); // iterationCount = 65536 // keyLength = 256 KeySpec spec = new PBEKeySpec(password, salt, 65536, 256); SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES"); return secret; }

Nous utilisons salt pour protéger les attaques arc-en-ciel, et c’est aussi un octet aléatoire, nous pouvons utiliser la même 1.1 getRandomNonce pour le générer.

1.4 Nous regroupons les méthodes ci-dessus dans une seule util classe, afin de ne pas répéter le même code encore et encore.

CryptoUtils.java
package com.mkyong.crypto.utils;import javax.crypto.KeyGenerator;import javax.crypto.SecretKey;import javax.crypto.SecretKeyFactory;import javax.crypto.spec.PBEKeySpec;import javax.crypto.spec.SecretKeySpec;import java.security.NoSuchAlgorithmException;import java.security.SecureRandom;import java.security.spec.InvalidKeySpecException;import java.security.spec.KeySpec;import java.util.ArrayList;import java.util.List;public class CryptoUtils { public static byte getRandomNonce(int numBytes) { byte nonce = new byte; new SecureRandom().nextBytes(nonce); return nonce; } // AES secret key public static SecretKey getAESKey(int keysize) throws NoSuchAlgorithmException { KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(keysize, SecureRandom.getInstanceStrong()); return keyGen.generateKey(); } // Password derived AES 256 bits secret key public static SecretKey getAESKeyFromPassword(char password, byte salt) throws NoSuchAlgorithmException, InvalidKeySpecException { SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); // iterationCount = 65536 // keyLength = 256 KeySpec spec = new PBEKeySpec(password, salt, 65536, 256); SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES"); return secret; } // hex representation public static String hex(byte bytes) { StringBuilder result = new StringBuilder(); for (byte b : bytes) { result.append(String.format("%02x", b)); } return result.toString(); } // print hex with block size split public static String hexWithBlockSize(byte bytes, int blockSize) { String hex = hex(bytes); // one hex = 2 chars blockSize = blockSize * 2; // better idea how to print this? List<String> result = new ArrayList<>(); int index = 0; while (index < hex.length()) { result.add(hex.substring(index, Math.min(index + blockSize, hex.length()))); index += blockSize; } return result.toString(); }}

Chiffrement et déchiffrement AES.

L’AES-GSM est le chiffrement authentifié le plus utilisé. Cet exemple va chiffrer et déchiffrer une chaîne de caractères en utilisant l’AES 256 bits en mode compteur de Galois (GCM).

Les entrées de l’AES-GCM:

  • Clé secrète AES (256 bits)
  • IV – 96 bits (12 octets)
  • Longueur (en bits) de la balise d’authentification – 128 bits (16 octets)

2.1. En Java, nous utilisons AES/GCM/NoPadding pour représenter l’algorithme AES-GCM. Pour la sortie chiffrée, nous préfixons le IV de 16 octets au texte chiffré (ciphertext), car nous avons besoin du même IV pour le déchiffrement.

C’est ok si le IV est connu publiquement ?
C’est ok si IV est connu publiquement, le seul secret est la clé, gardez-la privée et confidentielle.

Cet exemple va utiliser AES pour chiffrer un texte en clair Hello World AES-GCM et plus tard le déchiffrer pour retrouver le texte en clair original.

EncrypteurAesGcm.java
package com.mkyong.crypto.encryptor;import com.mkyong.crypto.utils.CryptoUtils;import javax.crypto.Cipher;import javax.crypto.SecretKey;import javax.crypto.spec.GCMParameterSpec;import java.nio.ByteBuffer;import java.nio.charset.Charset;import java.nio.charset.StandardCharsets;/** * AES-GCM inputs - 12 bytes IV, need the same IV and secret keys for encryption and decryption. * <p> * The output consist of iv, encrypted content, and auth tag in the following format: * output = byte {i i i c c c c c c ...} * <p> * i = IV bytes * c = content bytes (encrypted content, auth tag) */public class EncryptorAesGcm { private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding"; private static final int TAG_LENGTH_BIT = 128; private static final int IV_LENGTH_BYTE = 12; private static final int AES_KEY_BIT = 256; private static final Charset UTF_8 = StandardCharsets.UTF_8; // AES-GCM needs GCMParameterSpec public static byte encrypt(byte pText, SecretKey secret, byte iv) throws Exception { Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); cipher.init(Cipher.ENCRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); byte encryptedText = cipher.doFinal(pText); return encryptedText; } // prefix IV length + IV bytes to cipher text public static byte encryptWithPrefixIV(byte pText, SecretKey secret, byte iv) throws Exception { byte cipherText = encrypt(pText, secret, iv); byte cipherTextWithIv = ByteBuffer.allocate(iv.length + cipherText.length) .put(iv) .put(cipherText) .array(); return cipherTextWithIv; } public static String decrypt(byte cText, SecretKey secret, byte iv) throws Exception { Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); cipher.init(Cipher.DECRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); byte plainText = cipher.doFinal(cText); return new String(plainText, UTF_8); } public static String decryptWithPrefixIV(byte cText, SecretKey secret) throws Exception { ByteBuffer bb = ByteBuffer.wrap(cText); byte iv = new byte; bb.get(iv); //bb.get(iv, 0, iv.length); byte cipherText = new byte; bb.get(cipherText); String plainText = decrypt(cipherText, secret, iv); return plainText; } public static void main(String args) throws Exception { String OUTPUT_FORMAT = "%-30s:%s"; String pText = "Hello World AES-GCM, Welcome to Cryptography!"; // encrypt and decrypt need the same key. // get AES 256 bits (32 bytes) key SecretKey secretKey = CryptoUtils.getAESKey(AES_KEY_BIT); // encrypt and decrypt need the same IV. // AES-GCM needs IV 96-bit (12 bytes) byte iv = CryptoUtils.getRandomNonce(IV_LENGTH_BYTE); byte encryptedText = EncryptorAesGcm.encryptWithPrefixIV(pText.getBytes(UTF_8), secretKey, iv); System.out.println("\n------ AES GCM Encryption ------"); System.out.println(String.format(OUTPUT_FORMAT, "Input (plain text)", pText)); System.out.println(String.format(OUTPUT_FORMAT, "Key (hex)", CryptoUtils.hex(secretKey.getEncoded()))); System.out.println(String.format(OUTPUT_FORMAT, "IV (hex)", CryptoUtils.hex(iv))); System.out.println(String.format(OUTPUT_FORMAT, "Encrypted (hex) ", CryptoUtils.hex(encryptedText))); System.out.println(String.format(OUTPUT_FORMAT, "Encrypted (hex) (block = 16)", CryptoUtils.hexWithBlockSize(encryptedText, 16))); System.out.println("\n------ AES GCM Decryption ------"); System.out.println(String.format(OUTPUT_FORMAT, "Input (hex)", CryptoUtils.hex(encryptedText))); System.out.println(String.format(OUTPUT_FORMAT, "Input (hex) (block = 16)", CryptoUtils.hexWithBlockSize(encryptedText, 16))); System.out.println(String.format(OUTPUT_FORMAT, "Key (hex)", CryptoUtils.hex(secretKey.getEncoded()))); String decryptedText = EncryptorAesGcm.decryptWithPrefixIV(encryptedText, secretKey); System.out.println(String.format(OUTPUT_FORMAT, "Decrypted (plain text)", decryptedText)); }}

Sortie

Texte clair : Hello World AES-GCM

Terminal
------ AES GCM Encryption ------Input (plain text) :Hello World AES-GCMKey (hex) :603d87185bf855532f14a77a91ec7b025c004bf664e9f5c6e95613ee9577f436IV (hex) :bdb271ce5235996a0709e09cEncrypted (hex) :bdb271ce5235996a0709e09c2d03eefe319e9329768724755c56291aecaef88cd1e6bdf72b8c7b54d75a94e66b0cd3Encrypted (hex) (block = 16) :------ AES GCM Decryption ------Input (hex) :bdb271ce5235996a0709e09c2d03eefe319e9329768724755c56291aecaef88cd1e6bdf72b8c7b54d75a94e66b0cd3Input (hex) (block = 16) :Key (hex) :603d87185bf855532f14a77a91ec7b025c004bf664e9f5c6e95613ee9577f436Decrypted (plain text) :Hello World AES-GCM

Texte clair : Hello World AES-GCM, Welcome to Cryptography!

Terminal
------ AES GCM Encryption ------Input (plain text) :Hello World AES-GCM, Welcome to Cryptography!Key (hex) :ddc24663d104e1c2f81f11aef98156503dafdc435f81e3ac3d705015ebab095cIV (hex) :b05d6aedf023f73b9e1e2d11Encrypted (hex) :b05d6aedf023f73b9e1e2d11f6f5137d971aea8c5cdd5b045e0960eb4408e0ee4635cccc2dfeec2c13a89bd400f659be82dc2329e9c36e3b032f38bd42296a8495ac840b0625c097d9Encrypted (hex) (block = 16) :------ AES GCM Decryption ------Input (hex) :b05d6aedf023f73b9e1e2d11f6f5137d971aea8c5cdd5b045e0960eb4408e0ee4635cccc2dfeec2c13a89bd400f659be82dc2329e9c36e3b032f38bd42296a8495ac840b0625c097d9Input (hex) (block = 16) :Key (hex) :ddc24663d104e1c2f81f11aef98156503dafdc435f81e3ac3d705015ebab095cDecrypted (plain text) :Hello World AES-GCM, Welcome to Cryptography!

Chiffrement et déchiffrement par mot de passe AES.

Pour le chiffrement basé sur un mot de passe, nous pouvons utiliser la spécification de cryptographie basée sur un mot de passe (PKCS), définie RFC 8018, pour générer une clé à partir d’un mot de passe donné.

Pour les entrées PKCS :

  • Mot de passe, vous fournissez ceci.
  • Sel – Au moins 64 bits (8 octets) aléatoires.
  • Compte d’itération – Recommander un nombre d’itération minimum de 1 000.

Qu’est-ce que le sel et le compte d’itération?

  • La salt produit un large ensemble de clés pour un mot de passe donné. Par exemple, si le sel est de 128 bits, il y aura jusqu’à 2^128 clés pour chaque mot de passe. Par conséquent, cela augmente la difficulté des attaques arc-en-ciel. De plus, la table arc-en-ciel que les attaquants construisent pour le mot de passe d’un utilisateur est devenue inutile pour un autre utilisateur.
  • La iteration count augmentant le coût de production des clés à partir d’un mot de passe, donc augmentant la difficulté et ralentissant la vitesse des attaques.

3.1 Pour la sortie chiffrée, nous préfixons les 12 bytes IV et password salt au texte chiffré, car nous avons besoin du même IV et du même sel de mot de passe (pour la clé secrète) pour le déchiffrement. En outre, nous utilisons Base64 encodeur pour coder le texte crypté dans une représentation de chaîne, de sorte que nous pouvons envoyer le texte crypté ou le texte chiffré dans un format de chaîne (tableau d’octets de was).

C’est ok si le sel de mot de passe est connu publiquement ?
C’est la même chose avec IV, et c’est ok pour le sel de mot de passe d’être connu publiquement, le seul secret est la clé, en le gardant privé et confidentiel.

EncrypteurAesGcmPassword.java
package com.mkyong.crypto.encryptor;import com.mkyong.crypto.utils.CryptoUtils;import javax.crypto.Cipher;import javax.crypto.SecretKey;import javax.crypto.spec.GCMParameterSpec;import java.nio.ByteBuffer;import java.nio.charset.Charset;import java.nio.charset.StandardCharsets;import java.util.Base64;/** * AES-GCM inputs - 12 bytes IV, need the same IV and secret keys for encryption and decryption. * <p> * The output consist of iv, password's salt, encrypted content and auth tag in the following format: * output = byte {i i i s s s c c c c c c ...} * <p> * i = IV bytes * s = Salt bytes * c = content bytes (encrypted content) */public class EncryptorAesGcmPassword { private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding"; private static final int TAG_LENGTH_BIT = 128; // must be one of {128, 120, 112, 104, 96} private static final int IV_LENGTH_BYTE = 12; private static final int SALT_LENGTH_BYTE = 16; private static final Charset UTF_8 = StandardCharsets.UTF_8; // return a base64 encoded AES encrypted text public static String encrypt(byte pText, String password) throws Exception { // 16 bytes salt byte salt = CryptoUtils.getRandomNonce(SALT_LENGTH_BYTE); // GCM recommended 12 bytes iv? byte iv = CryptoUtils.getRandomNonce(IV_LENGTH_BYTE); // secret key from password SecretKey aesKeyFromPassword = CryptoUtils.getAESKeyFromPassword(password.toCharArray(), salt); Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); // ASE-GCM needs GCMParameterSpec cipher.init(Cipher.ENCRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); byte cipherText = cipher.doFinal(pText); // prefix IV and Salt to cipher text byte cipherTextWithIvSalt = ByteBuffer.allocate(iv.length + salt.length + cipherText.length) .put(iv) .put(salt) .put(cipherText) .array(); // string representation, base64, send this string to other for decryption. return Base64.getEncoder().encodeToString(cipherTextWithIvSalt); } // we need the same password, salt and iv to decrypt it private static String decrypt(String cText, String password) throws Exception { byte decode = Base64.getDecoder().decode(cText.getBytes(UTF_8)); // get back the iv and salt from the cipher text ByteBuffer bb = ByteBuffer.wrap(decode); byte iv = new byte; bb.get(iv); byte salt = new byte; bb.get(salt); byte cipherText = new byte; bb.get(cipherText); // get back the aes key from the same password and salt SecretKey aesKeyFromPassword = CryptoUtils.getAESKeyFromPassword(password.toCharArray(), salt); Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); cipher.init(Cipher.DECRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); byte plainText = cipher.doFinal(cipherText); return new String(plainText, UTF_8); } public static void main(String args) throws Exception { String OUTPUT_FORMAT = "%-30s:%s"; String PASSWORD = "this is a password"; String pText = "AES-GSM Password-Bases encryption!"; String encryptedTextBase64 = EncryptorAesGcmPassword.encrypt(pText.getBytes(UTF_8), PASSWORD); System.out.println("\n------ AES GCM Password-based Encryption ------"); System.out.println(String.format(OUTPUT_FORMAT, "Input (plain text)", pText)); System.out.println(String.format(OUTPUT_FORMAT, "Encrypted (base64) ", encryptedTextBase64)); System.out.println("\n------ AES GCM Password-based Decryption ------"); System.out.println(String.format(OUTPUT_FORMAT, "Input (base64)", encryptedTextBase64)); String decryptedText = EncryptorAesGcmPassword.decrypt(encryptedTextBase64, PASSWORD); System.out.println(String.format(OUTPUT_FORMAT, "Decrypted (plain text)", decryptedText)); }}

Sortie

Terminal
------ AES GCM Password-based Encryption ------Input (plain text) :AES-GSM Password-Bases encryption!Encrypted (base64) :KmrvjnMusJTQo/hB7T5BvlQpvi3bVbdjpZP51NT7I/enrIfSQuDfSK6iXgdPzvUP2IE54mwrKiyHqMkG8224lRZ9tXHcclmdh98I8b3B------ AES GCM Password-based Decryption ------Input (base64) :KmrvjnMusJTQo/hB7T5BvlQpvi3bVbdjpZP51NT7I/enrIfSQuDfSK6iXgdPzvUP2IE54mwrKiyHqMkG8224lRZ9tXHcclmdh98I8b3BDecrypted (plain text) :AES-GSM Password-Bases encryption!

3.2 Si le mot de passe ne correspond pas, Java lance AEADBadTagException: Tag mismatch!

 // change the password to something else String decryptedText = EncryptorAesGcmPassword.decrypt(encryptedTextBase64, "other password"); System.out.println(String.format(OUTPUT_FORMAT, "Decrypted (plain text)", decryptedText));

Output

Terminal
Exception in thread "main" javax.crypto.AEADBadTagException: Tag mismatch! at java.base/com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:623) at java.base/com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1118) at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1055) at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:855) at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446) at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2207) at com.mkyong.crypto.encryptor.EncryptorAesGcmPassword.decrypt(EncryptorAesGcmPassword.java:88) at com.mkyong.crypto.encryptor.EncryptorAesGcmPassword.main(EncryptorAesGcmPassword.java:109)

Chiffrement et déchiffrement de fichiers AES.

Cet exemple est un chiffrement de fichier par mot de passe AES. Les idées sont les mêmes, mais nous avons besoin de quelques classes IO pour travailler avec les ressources ou les fichiers.

Voici un fichier texte, au resources dossier.

readme.txt
This is line 1.This is line 2.This is line 3.This is line 4.This is line 5.This is line 9.This is line 10.

4.1 Cet exemple est similaire à 3.1 EncryptorAesGcmPassword.java, avec quelques changements mineurs comme renvoyer un byte au lieu d’une chaîne encodée en base64.

 public static byte encrypt(byte pText, String password) throws Exception { //... // prefix IV and Salt to cipher text byte cipherTextWithIvSalt = ByteBuffer.allocate(iv.length + salt.length + cipherText.length) .put(iv) .put(salt) .put(cipherText) .array(); // it works, even if we save the based64 encoded string into a file. // return Base64.getEncoder().encodeToString(cipherTextWithIvSalt); // we save the byte into a file. return cipherTextWithIvSalt; }

Ajouter encryptFile et decryptFile pour travailler avec le fichier.

 public static void encryptFile(String fromFile, String toFile, String password) throws Exception { // read a normal txt file byte fileContent = Files.readAllBytes(Paths.get(ClassLoader.getSystemResource(fromFile).toURI())); // encrypt with a password byte encryptedText = EncryptorAesGcmPasswordFile.encrypt(fileContent, password); // save a file Path path = Paths.get(toFile); Files.write(path, encryptedText); } public static byte decryptFile(String fromEncryptedFile, String password) throws Exception { // read a file byte fileContent = Files.readAllBytes(Paths.get(fromEncryptedFile)); return EncryptorAesGcmPasswordFile.decrypt(fileContent, password); }

4.2 Lire le fichier ci-dessus readme.txt à partir du classpath, le chiffrer, et les données chiffrées dans un nouveau fichier c:\test\readme.encrypted.txt.

 String password = "password123"; String fromFile = "readme.txt"; // from resources folder String toFile = "c:\\test\\readme.encrypted.txt"; // encrypt file EncryptorAesGcmPasswordFile.encryptFile(fromFile, toFile, password);

Sortie

Cryptage de fichier AES

4.3 Lire le fichier crypté, le décrypter et imprimer la sortie.

 String password = "password123"; String toFile = "c:\\test\\readme.encrypted.txt"; // decrypt file byte decryptedText = EncryptorAesGcmPasswordFile.decryptFile(toFile, password); String pText = new String(decryptedText, UTF_8); System.out.println(pText);

Sortie

Terminal
This is line 1.This is line 2.This is line 3.This is line 4.This is line 5.This is line 9.This is line 10.

P.S Le chiffrement AES des images relève du même concept.

Télécharger le code source

$ git clone https://github.com/mkyong/core-java

$ cd java-crypto

Laissez-moi savoir si l’article doit être amélioré. Merci.

  • Wikipedia – Cipher JavaDoc
  • Wikipedia – Cipher Block Chaining (CBC)
  • Wikipedia – Galois/Counter Mode (GCM)
  • Oracle – KeyGenerator Algorithms JavaDoc
  • Java – Comment générer un 12 octets aléatoire ?
  • Pourquoi ne devrais-je pas utiliser le cryptage ECB ?
  • Spring Security Crypto Module
  • Wikipedia – PBKDF2
  • RFC 8018 – PKCS
  • Java – Comment joindre et diviser un tableau d’octets
  • Norme de sécurité Java. Noms d’algorithmes
  • NIST – Recommandation pour le mode galois/compteur (GCM)
image de l'auteur

mkyong

Fondateur de Mkyong.com, adore Java et les trucs open source. Suivez-le sur Twitter. Si vous aimez mes tutoriels, pensez à faire un don à ces organismes de bienfaisance.

.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *