Post Thumbnail

How to Encrypt and Decrypt Data using AES in Java

1. Overview

Advanced Encryption Standard (AES) is a symmetric algorithm for encrypting data to improve the security of a system. It is a “symmetric” algorithm because it uses the same key to encrypt and decrypt data.

One of the advantages of using AES is that it is fast to execute with small resource footprint. This means it can be used in systems with high throughput requirements.

For example, a banking application that processes millions of transactions per minute can use AES to encrypt customer data in transit and at rest.

Because AES uses a single key for decryption and encryption, the key should be handled with utmost care and shared via a secure channel.

2. AES Data Encryption

Encrypting data using AES in Java requires the creation of a SecretKey object to encapsulate the secret key and the construction of a suitable Cipher to use in the encryption.

Listing 2.1 AesService.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static String encryptDataAES(String plainData, byte[] key) throws Exception {

    SecretKey secretKey = new SecretKeySpec(key, "AES");

    SecureRandom secureRandom = new SecureRandom();

    //build the initialization vector
    byte[] iv = new byte[CRYPTO_IV_LENGTH];
    secureRandom.nextBytes(iv);

    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    
    //CRYPTO_AUTH_TAG_LENGTH's value is 128
    GCMParameterSpec parameterSpec = new GCMParameterSpec(CRYPTO_AUTH_TAG_LENGTH, iv); 
    cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);

    byte[] cipherText = cipher.doFinal(plainData.getBytes());

    ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
    byteBuffer.put(iv); //the first 12 bytes are the IV
    byteBuffer.put(cipherText);

    
    byte[] cipherMessage = byteBuffer.array();
    return Base64.getEncoder().encodeToString(cipherMessage);

}

In snippet above, we constructed an instance of the Cipher class with the following param: AES/GCM/NoPadding.

AES is the encryption algorithm of choice and GCM (Galois/counter) is the mode of operation we preferred.

AES has many modes of operation, and depending on the preferred mode, there is a need for an Initialization Vector (IV).

In cryptography, an Initialization Vector refers to the bits that are used to randomize the encryption process.

This ensures that each encryption operation results in a different cipher text—even if the plain data is the same. Using an IV serves as an additional layer of security that protects against ciphertext pattern attacks.

Because our preferred mode of operation is GCM, we are required to generate unique, non-repeating, secure bits for every encryption operation.

This is why we are generating secure random bytes in lines 7–9 and passing it to the GCMParameterSpec constructor. We also configure the spec to use a 128-bit authentication tag length.

Authentication tag is used to verify that the resulting ciphertext has not been tampered with during the decryption process. 128-bit is the recommended configuration when using the GCM mode for AES encryption.

We will need the IV bytes again when we want to decrypt the data, that’s why we prepend it to the actual bytes of the ciphertext, and encode the result to Base64.

The Base64 format choice is simply because it’s easier to transport (especially on the web) and store a String data type in applications as opposed to raw bytes.

3. AES Data Decryption

Decrypting data in AES requires using the same cryptographic parameters used in the encryption process and the same key.

Therefore, we will use the same cipher params AES/GCM/NoPadding to build a Cipher object. To produce the ciphertext in bytes, we will decode the data Base64 String data.

Remember that we prepend the IV to the actual cipher text when encrypting? We will reconstruct the IV by extracting the first 12 bytes of the decoded Base64 data and use it to construct the GCMParameterSpec.

Finally, we will extract the actual ciphertext from the decoded base64 data and decrypt it.

The resulting bytes are used to construct a new String instance - which is the plain data.

Listing 3.1 AesService.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static String decryptDataAES(String encryptedDataInBase64, byte[] key) 
        throws Exception {

    SecretKey secretKey = new SecretKeySpec(key, "AES");

    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

    byte[] encryptedDataBytes = Base64.getDecoder()
            .decode(encryptedDataInBase64.getBytes());

    //remember we stored the IV as the first 12 bytes while encrypting?
    byte[] iv = Arrays.copyOfRange(encryptedDataBytes, 0, CRYPTO_IV_LENGTH);

    GCMParameterSpec parameterSpec = new GCMParameterSpec(CRYPTO_AUTH_TAG_LENGTH, iv);
    cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);

    //use everything from 12 bytes on as ciphertext
    byte [] cipherBytes = Arrays.copyOfRange(encryptedDataBytes, CRYPTO_IV_LENGTH,
            encryptedDataBytes.length);
    
    byte[] plainText = cipher.doFinal(cipherBytes);

    return new String(plainText);

}

We can test the two functions together using a simple JUnit test:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Test
void givenAESKey_whenEncryptDataAESAndDecryptDataAES_thenGetSamePlainData() 
        throws Exception {

    String aesKey = AesService.generateSecureAESKey(32);
    Assertions.assertEquals(32, aesKey.length());

    byte[] aesKeyBytes = aesKey.getBytes();

    String plainSecretData = "The World will end on ...";

    //encrypt
    String encryptedData = AesService.encryptDataAES(plainSecretData, aesKeyBytes);

   //decrypt
    String decryptedData = AesService.decryptDataAES(encryptedData, aesKeyBytes);

    //assert
    assertEquals(plainSecretData, decryptedData);
}

4. Conclusion

In today’s tutorial, we have learnt about the foundation of encrypting and decrypting sensitive data in Java.

This knowledge provides the right foundation for applying AES encryption in other aspects of software Engineering like encrypting customers’ data or request and responses for a web application.

The complete source code is available on GitHub

Further reading: https://proandroiddev.com/security-best-practices-symmetric-encryption-with-aes-in-java-7616beaaade9

Happy Coding!

Seun Matt

Results-driven Engineer, dedicated to building elite teams that consistently achieve business objectives and drive profitability. With over 8 years of experience, spannning different facets of the FinTech space; including digital lending, consumer payment, collections and payment gateway using Java/Spring Boot technologies, PHP and Ruby on Rails