github linkedin
AES-CTR encryption with Java
2020-04-22

And another post about my pet peeve “crypto stuff”. This time I want to encrypt something with the AES cipher in CTR mode.

The advantages of CTR are, quote wikipedia:

CTR mode … also allows a random access property during decryption. CTR mode is well suited to operate on a multi-processor machine where blocks can be encrypted in parallel

But keep in mind that CTR isn’t an AEAD mode, meaning you have to secure the ciphertext with additional tamper proofing, e.g. a HMAC. If you need AEAD, take a look at AES/GCM which is essentially CTR with an authentication tag.

I couldn’t find any useful examples how to use AES/CTR with Java, so I investigated a little bit myself:

SecureRandom secureRandom = new SecureRandom();

// First, create the cipher
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
// Then generate the key. Can be 128, 192 or 256 bit
byte[] key = new byte[256 / 8];
secureRandom.nextBytes(key);
// Now generate a nonce. You can also use an ever-increasing counter, which is even more secure. NEVER REUSE A NONCE!
byte[] nonce = new byte[96 / 8];
secureRandom.nextBytes(nonce);

The nonce in my example is 96 bits. The IV of AES is always 128 bit regardless of the key length, meaning this leaves 32 bit (128 bit - 96 bit) for the counter which gives CTR (counter mode) its name.

With a nonce of 96 bits, you can encrypt 2^32 blocks (a block is always 128 bit in size) without repeating the counter. Repeating the counter is bad, very bad. If you set the nonce to 64 bits, you leave 64 bits for the counter, meaning you can encrypt 2^64 blocks. There’s a trade-off involved between a bigger nonce (use if you send lots of small messages) and a bigger counter (use if you send few large messages).

// IV is a nonce followed by a counter (starting at 0). The IV is always 128 bit long.
// IV in hex looks for example: a2591afec0b2575c50943f2100000000
//                              |nonce                  |counter
byte[] iv = new byte[128 / 8];
System.arraycopy(nonce, 0, iv, 0, nonce.length);
// No need to explicitly set the counter to 0, as Java arrays are initialized with 0 anyway

Now we can initialize the cipher and then encrypt data with it:

Key keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);

cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

byte[] plaintext = "Hello World CTR".getBytes(StandardCharsets.UTF_8);
byte[] ciphertext = cipher.doFinal(plaintext);

To decrypt the ciphertext, you’ll need 3 pieces of information: the ciphertext itself (can be transmitted over insecure channel), the nonce (can be transmitted over insecure channel, too) and the key (must be kept secret).

Create the cipher in decrypt mode using the 3 pieces of information mentioned above:

Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
// Use same nonce
byte[] iv = new byte[128 / 8];
System.arraycopy(nonce, 0, iv, 0, nonce.length);

// And use same key to decrypt
Key keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);

cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] plaintext = cipher.doFinal(ciphertext);
String plaintextString = new String(plaintext, StandardCharsets.UTF_8);

Remember: never reuse a nonce. NEVER!

If you want to use a counter as a nonce (which is a very good idea if you can guarantee it is never repeated), this is how you can convert a long into a byte[]:

return ByteBuffer.allocate(128 / 8).putLong(counter).array();

This will put your counter in the first 64 bit of the IV, and leaves 64 bit for the AES internal counter. With this construction, you can encrypt 2^64 blocks before you have to increase your counter.


Back to posts