Difference between IvParameterSpec and GCMParameterSpec with AES/GCM/NoPadding

Droid picture Droid · Apr 10, 2018 · Viewed 7.6k times · Source

I am using AES/GCM/NoPadding algorithm to encrypt some data on Android (API 19 and onwards) and then later decrypt it back.

The key size I use is 32 bytes and is provided to me

In addition to the encryption, I also want to know when I try to decrypt and use a wrong key. Which is why I prefer to use GCM as my mode to get the benefits of verifying integrity (I believe its safe to assume whether the ciphertext or the key whichever is faulty would result in a bad decrypt exception rather than garbled text)

The problem I face is that on Android API 19 using the algorithm above and initializing the cipher with GCMParameterSpec I get a NoSuchAlgorithmException, I do not specify any provider myself allowing Android to pick one for me which can support my algorithm. On 21+ the Algorithm is available. This is how I am initializing(similar for decryption), the entire class is posted at the end of this post.

cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(TAG_LENGTH_BIT, iv));

However, if I use IvParameterSpec(iv) as my AlgorithmParameters instead of GCMParameterSpec then the code works fine.

So what happens by changing these parameters? Do I still get all the same benefits of GCM?

Because the exceptions thrown are different when attempting to use a wrong key. On API 19 BadPaddingException is thrown when IvParameterSpec is used, on API 21+ AEADBADTagException is thrown when GCMParameterSpec is used.

Is it correct and secure to use just the IvParameterSpec through all the Android API levels and verify the integrity through BadPaddingException? I do not want to have different implementations for different platforms so I would want to use one only.

Also, on API 21+, if I encrypt using GCMParameterSpec and then later use IvParameterSpec to decrypt it decrypts fine! and the same vice versa. How is that working?

If the above is not possible on API 19 then what are my possible options to use as an encryption algorithm and a strategy to use(AES/CBC/PKCS5Padding with HMAC?) to verify the integrity of the key.

Full class code:

import android.util.Base64;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

final class Encryption {
    private static final String ALGORITHM = "AES/GCM/NoPadding";
    private static final int TAG_LENGTH_BIT = 128;
    private static final int IV_LENGTH_BYTE = 12;

    private final SecureRandom secureRandom;
    private Cipher cipher;
    private final Charset charset = StandardCharsets.UTF_8;

    public Encryption() {
        secureRandom = new SecureRandom();
    }

    public String encrypt(byte[] key, String rawData) throws Exception {
        try {
            byte[] iv = new byte[IV_LENGTH_BYTE];
            secureRandom.nextBytes(iv);

            cipher = Cipher.getInstance(ALGORITHM);
            //This is where I switch to IvParameterSpec(iv)
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(TAG_LENGTH_BIT, iv));

            byte[] encrypted = cipher.doFinal(rawData.getBytes(charset));

            ByteBuffer byteBuffer = ByteBuffer.allocate(1 + iv.length + encrypted.length);
            byteBuffer.put((byte) iv.length);
            byteBuffer.put(iv);
            byteBuffer.put(encrypted);
            return Base64.encodeToString(byteBuffer.array(), Base64.NO_WRAP);
        } catch (Exception e) { //ignore this SO
            throw new Exception(e);
        }
    }


    public String decrypt(byte[] key, String encryptedData) throws Exception {
        try {
            ByteBuffer byteBuffer = ByteBuffer.wrap(Base64.decode(encryptedData, Base64.NO_WRAP));

            int ivLength = byteBuffer.get();
            byte[] iv = new byte[ivLength];
            byteBuffer.get(iv);
            byte[] encrypted = new byte[byteBuffer.remaining()];
            byteBuffer.get(encrypted);

            cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(TAG_LENGTH_BIT, iv));
            byte[] decrypted = cipher.doFinal(encrypted);

            //Paranoia
            Arrays.fill(iv, (byte) 0);
            Arrays.fill(rawEncryptionKey, (byte) 0);
            Arrays.fill(encrypted, (byte) 0);

            return new String(decrypted, charset);
        } catch (Exception e) { //ignore this SO
            // On API 19 BadPaddingException is thrown when IvParameterSpec is used
            // On API 21+ AEADBADTagException is thrown
            throw new Exception("could not decrypt", e);
        }
    }
}

Also, feel free to suggest improvements to the provided class along with your answer, thanks.

Answer

Maarten Bodewes picture Maarten Bodewes · Apr 10, 2018

I also want to know when I try to decrypt and use a wrong key.

That's OK, but please understand that an invalid tag can mean that the tag itself was altered, the ciphertext was altered, the IV was altered, the AAD was altered or that indeed the key is incorrect.

You could also use a key check value or something similar to check if the key size is correct or not before decrypting. But note that an adversary could also alter that check value.

So what happens by changing these parameters? Do I still get all the same benefits of GCM?

For sure, but GCM was retrofitted in such a way that it was largely compatible but still has more configuration options (mainly the tag size) - if you require to configure that. The AEADBADTagException is a BadPaddingException so the code should work for each, even though AEADBADTagException is more specific.

Is it correct and secure to use just the IvParameterSpec through all the Android API levels and verify the integrity through BadPaddingException? I do not want to have different implementations for different platforms so I would want to use one only.

For sure. Note that only the tag could throw a BadPaddingException, so such an exception does correctly identify a problem with the authentication.

Also, on API 21+, if I encrypt using GCMParameterSpec and then later use IvParameterSpec to decrypt it decrypts fine! and the same vice versa. How is that working?

Your code is running for each type of parameter specification because you specified the same tag size as the default: 128 bits. It wouldn't work with a smaller tag size.


Code comments:

  • charset should be a constant (static final);
  • keys should not be passed as byte arrays but as SecretKey instances;
  • the IV should always be 12 bytes, so the IV size doesn't need to be communicated;
  • if you do communicate the IV size then you need to check if it is a valid value, currently an adversary can control that byte (and let you create a large IV or throw in ArrayIndexOutOfBounds exception);
  • when processing exceptions you will want to differentiate between code issues (GCM algorithm not available) and input related issues (bad size) - I wrote a small primer as answer here;
  • currently your code is OK for small messages; some kind of streaming would be nice for larger messages.