Cifrado y descifrado Java AES

Parte superior de Java

Acabo de anunciar el nuevo curso Learn Spring , centrado en los fundamentos de Spring 5 y Spring Boot 2:

>> VER EL CURSO

1. Información general

El cifrado de bloque de clave simétrica juega un papel importante en el cifrado de datos. Significa que se utiliza la misma clave tanto para el cifrado como para el descifrado. El Estándar de cifrado avanzado (AES) es un algoritmo de cifrado de clave simétrica ampliamente utilizado.

En este tutorial, veremos cómo implementar el cifrado y descifrado AES utilizando la arquitectura de criptografía de Java (JCA) dentro del JDK.

2. Algoritmo AES

El algoritmo AES es un cifrado de bloque de clave simétrica iterativo que admite claves criptográficas (claves secretas) de 128, 192 y 256 bits para cifrar y descifrar datos en bloques de 128 bits . La siguiente figura muestra el algoritmo AES de alto nivel:

Si los datos que se van a cifrar no cumplen con el requisito de tamaño de bloque de 128 bits, deben rellenarse. El relleno es un proceso de llenar el último bloque a 128 bits.

3. Variaciones de AES

El algoritmo AES tiene seis modos de funcionamiento:

  1. BCE (Libro de códigos electrónico)
  2. CBC (encadenamiento de bloques de cifrado)
  3. CFB (retroalimentación de cifrado)
  4. OFB (realimentación de salida)
  5. CTR (contador)
  6. GCM (Modo Galois / Contador)

El modo de funcionamiento puede aplicarse para fortalecer el efecto del algoritmo de cifrado. Además, el modo de funcionamiento puede convertir el cifrado de bloques en un cifrado de flujo. Cada modo tiene sus fortalezas y debilidades. Hagamos un repaso rápido.

3.1. ECB

Este modo de funcionamiento es el más sencillo de todos. El texto sin formato se divide en bloques con un tamaño de 128 bits. Luego, cada bloque se cifrará con la misma clave y algoritmo. Por lo tanto, produce el mismo resultado para el mismo bloque. Esta es la principal debilidad de este modo y no se recomienda para el cifrado . Requiere datos de relleno.

3.2. CBC

Para superar la debilidad de ECB, el modo CBC utiliza un vector de inicialización (IV) para aumentar el cifrado. Primero, CBC usa el bloque de texto plano xor con el IV. Luego cifra el resultado en el bloque de texto cifrado. En el siguiente bloque, usa el resultado del cifrado para xor con el bloque de texto plano hasta el último bloque.

En este modo, el cifrado no se puede paralelizar, pero el descifrado se puede paralelizar. También requiere datos de relleno.

3.3. CFB

Este modo se puede utilizar como cifrado de flujo. Primero, encripta el IV, luego xor con el bloque de texto plano para obtener el texto cifrado. Luego, CFB cifra el resultado del cifrado para xo el texto sin formato. Necesita una vía intravenosa.

En este modo, el descifrado se puede paralelizar pero el cifrado no se puede paralelizar.

3.4. DE B

Este modo se puede utilizar como un cifrado de flujo. Primero, cifra el IV. Luego usa los resultados del cifrado para xo el texto sin formato para obtener texto cifrado.

No requiere datos de relleno y no se verá afectado por el bloque ruidoso.

3.5. CTR

Este modo usa el valor de un contador como un IV. Es muy similar a OFB, pero usa el contador para encriptarse cada vez en lugar del IV.

Este modo tiene dos puntos fuertes, incluida la paralelización de cifrado / descifrado, y el ruido en un bloque no afecta a otros bloques.

3.6. GCM

Este modo es una extensión del modo CTR. El GCM ha recibido una atención significativa y es recomendado por NIST. El modelo de GCM genera un texto cifrado y una etiqueta de autenticación. La principal ventaja de este modo, en comparación con otros modos de operación del algoritmo, es su eficiencia.

En este tutorial, usaremos el algoritmo AES / CBC / PKCS5Padding porque se usa ampliamente en muchos proyectos.

3.7. Tamaño de los datos después del cifrado

Como se mencionó anteriormente, el AES tiene un tamaño de bloque de 128 bits o 16 bytes. El AES no cambia el tamaño y el tamaño del texto cifrado es igual al tamaño del texto sin cifrar. Además, en los modos ECB y CBC, deberíamos usar un algoritmo de relleno como PKCS 5. Entonces, el tamaño de los datos después del cifrado es:

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

Para almacenar IV con texto cifrado, necesitamos agregar 16 bytes más.

4. Parámetros AES

En el algoritmo AES, necesitamos tres parámetros: datos de entrada, clave secreta e IV. IV no se utiliza en el modo ECB.

4.1. Los datos de entrada

Los datos de entrada al AES pueden estar basados ​​en cadenas, archivos, objetos y contraseñas.

4.2. Llave secreta

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 resumen, hemos aprendido cómo cifrar y descifrar datos de entrada como cadenas, archivos, objetos y datos basados ​​en contraseñas, utilizando el algoritmo AES en Java. Además, hemos discutido las variaciones de AES y el tamaño de los datos después del cifrado.

Como siempre, el código fuente completo del artículo está disponible en GitHub.

Fondo de Java

Acabo de anunciar el nuevo curso Learn Spring , centrado en los fundamentos de Spring 5 y Spring Boot 2:

>> VER EL CURSO