Crittografia e decrittografia Java AES

Java Top

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO

1. Panoramica

La crittografia a blocchi a chiave simmetrica svolge un ruolo importante nella crittografia dei dati. Significa che la stessa chiave viene utilizzata sia per la crittografia che per la decrittografia. L'Advanced Encryption Standard (AES) è un algoritmo di crittografia a chiave simmetrica ampiamente utilizzato.

In questo tutorial vedremo come implementare la crittografia e la decrittografia AES utilizzando Java Cryptography Architecture (JCA) all'interno di JDK.

2. Algoritmo AES

L'algoritmo AES è un cifrario a blocchi iterativo a chiave simmetrica che supporta chiavi crittografiche (chiavi segrete) di 128, 192 e 256 bit per crittografare e decrittografare i dati in blocchi di 128 bit . La figura seguente mostra l'algoritmo AES di alto livello:

Se i dati da crittografare non soddisfano la dimensione del blocco di 128 bit, devono essere riempiti. Il riempimento è un processo di riempimento dell'ultimo blocco a 128 bit.

3. Variazioni AES

L'algoritmo AES ha sei modalità di funzionamento:

  1. ECB (Electronic Code Book)
  2. CBC (Cipher Block Chaining)
  3. CFB (Cipher FeedBack)
  4. OFB (Output FeedBack)
  5. CTR (contatore)
  6. GCM (Galois / Modalità contatore)

La modalità di funzionamento può essere applicata al fine di rafforzare l'effetto dell'algoritmo di crittografia. Inoltre, la modalità di funzionamento può convertire il cifrario a blocchi in un cifrario a flusso. Ogni modalità ha i suoi punti di forza e di debolezza. Facciamo una rapida recensione.

3.1. ECB

Questa modalità di funzionamento è la più semplice di tutte. Il testo in chiaro è diviso in blocchi con una dimensione di 128 bit. Quindi ogni blocco verrà crittografato con la stessa chiave e algoritmo. Pertanto, produce lo stesso risultato per lo stesso blocco. Questo è il principale punto debole di questa modalità e non è consigliato per la crittografia . Richiede dati di riempimento.

3.2. CBC

Per superare la debolezza della BCE, la modalità CBC utilizza un Initialization Vector (IV) per aumentare la crittografia. Innanzitutto, CBC utilizza il blocco di testo in chiaro xor con IV. Quindi crittografa il risultato nel blocco di testo cifrato. Nel blocco successivo, utilizza il risultato della crittografia in xor con il blocco di testo in chiaro fino all'ultimo blocco.

In questa modalità, la crittografia non può essere parallelizzata, ma la decrittografia può essere parallelizzata. Richiede anche dati di riempimento.

3.3. CFB

Questa modalità può essere utilizzata come cifrario a flusso. In primo luogo, crittografa l'IV, quindi si xor con il blocco di testo in chiaro per ottenere il testo cifrato. Quindi CFB crittografa il risultato della crittografia in xor il testo in chiaro. Ha bisogno di una flebo.

In questa modalità, la decrittografia può essere parallelizzata ma la crittografia non può essere parallelizzata.

3.4. OFB

Questa modalità può essere utilizzata come cifrario a flusso. Innanzitutto, crittografa IV. Quindi utilizza i risultati della crittografia per xo il testo in chiaro per ottenere il testo cifrato.

Non richiede dati di riempimento e non sarà influenzato dal blocco rumoroso.

3.5. CTR

Questa modalità utilizza il valore di un contatore come IV. È molto simile a OFB, ma utilizza il contatore per essere crittografato ogni volta invece dell'IV.

Questa modalità ha due punti di forza, inclusa la parallelizzazione di crittografia / decrittografia e il rumore in un blocco non influisce sugli altri blocchi.

3.6. GCM

Questa modalità è un'estensione della modalità CTR. Il GCM ha ricevuto un'attenzione significativa ed è raccomandato dal NIST. Il modello GCM restituisce testo cifrato e un tag di autenticazione. Il vantaggio principale di questa modalità, rispetto ad altre modalità operative dell'algoritmo, è la sua efficienza.

In questo tutorial, utilizzeremo l' algoritmo AES / CBC / PKCS5Padding perché è ampiamente utilizzato in molti progetti.

3.7. Dimensioni dei dati dopo la crittografia

Come accennato in precedenza, l'AES ha una dimensione del blocco di 128 bit o 16 byte. L'AES non cambia la dimensione e la dimensione del testo cifrato è uguale alla dimensione del testo in chiaro. Inoltre, nelle modalità ECB e CBC, dovremmo utilizzare un algoritmo di riempimento come PKCS 5. Quindi, la dimensione dei dati dopo la crittografia è:

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

Per memorizzare IV con testo cifrato, dobbiamo aggiungere altri 16 byte.

4. Parametri AES

Nell'algoritmo AES, abbiamo bisogno di tre parametri: dati di input, chiave segreta e IV. IV non è utilizzato in modalità ECB.

4.1. Dati in ingresso

I dati di input in AES possono essere basati su stringhe, file, oggetti e password.

4.2. Chiave segreta

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

In sintesi, abbiamo imparato come crittografare e decrittografare i dati di input come stringhe, file, oggetti e dati basati su password, utilizzando l'algoritmo AES in Java. Inoltre, abbiamo discusso le variazioni AES e la dimensione dei dati dopo la crittografia.

Come sempre, il codice sorgente completo dell'articolo è disponibile su GitHub.

Fondo Java

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO