Q: Initial bytes incorrect after Java AES/CBC decryption
Table of Contents
In this answer I choose to approach the “Simple Java AES encrypt/decrypt example” main theme and not the specific debugging question because I think this will profit most readers.
This is a simple summary of my blog post about AES encryption in Java so I recommend reading through it before implementing anything. I will however still provide a simple example to use and give some pointers what to watch out for.
In this example I will choose to use authenticated encryption with Galois/Counter Mode or GCM mode. The reason is that in most case you want integrity and authenticity in combination with confidentiality (read more in the blog).
AES-GCM Encryption/Decryption Tutorial #
Here are the steps required to encrypt/decrypt with AES-GCM with the Java Cryptography Architecture (JCA). Do not mix with other examples, as subtle differences may make your code utterly insecure.
1. Create Key #
As it depends on your use-case, I will assume the simplest case: a random secret key.
SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, "AES");
Important:
- always use a strong pseudorandom number generator like
SecureRandom
- use 16 byte / 128 bit long key (or more - but more is seldom needed)
- if you want a key derived from a user password, look into a password hash function (or KDF) with stretching property like PBKDF2 or bcrypt
- if you want a key derived from other sources, use a proper key derivation function (KDF) like HKDF (Java implementation here). Do not use simple cryptographic hashes for that (like SHA-256).
2. Create the Initialization Vector #
An initialization vector (IV) is used so that the same secret key will create different cipher texts.
byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);
Important:
- never reuse the same IV with the same key (very important in GCM/CTR mode)
- the IV must be unique (ie. use random IV or a counter)
- the IV is not required to be secret
- always use a strong pseudorandom number generator like
SecureRandom
- 12 byte IV is the correct choice for AES-GCM mode
3. Encrypt with IV and Key #
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText);
Important:
- use 16 byte / 128 bit authentication tag (used to verify integrity/authenticity)
- the authentication tag will be automatically appended to the cipher text (in the JCA implementation)
- since GCM behaves like a stream cipher, no padding is required
- use
CipherInputStream
when encrypting large chunks of data - want additional (non-secret) data checked if it was changed? You may want to use associated data with
cipher.updateAAD(associatedData);
More here.
3. Serialize to Single Message #
Just append IV and ciphertext. As stated above, the IV doesn’t need to be secret.
ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();
Optionally encode with Base64 if you need a string representation. Either use Android’s or Java 8’s built-in implementation (do not use Apache Commons Codec - it’s an awful implementation). Encoding is used to “convert” byte arrays to string representation to make it ASCII safe e.g.:
String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);
4. Prepare Decryption: Deserialize #
If you have encoded the message, first decode it to byte array:
byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)
Important:
- be careful to validate input parameters, so to avoid denial of service attacks by allocating too much memory.
5. Decrypt #
Initialize the cipher and set the same parameters as with the encryption:
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//use first 12 bytes for iv
AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, 12);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv);
//use everything from 12 bytes on as ciphertext
byte[] plainText = cipher.doFinal(cipherMessage, 12, cipherMessage.length - 12);
Important:
- don’t forget to add associated data with
cipher.updateAAD(associatedData);
if you added it during encryption.
A working code snippet can be found in this gist.
Note that most recent Android (SDK 21+) and Java (7+) implementations should have AES-GCM. Older versions may lack it. I still choose this mode, since it is easier to implement in addition to being more efficient compared to similar mode of Encrypt-then-Mac (with e.g. AES-CBC + HMAC). See this article on how to implement AES-CBC with HMAC.