Java – IvParameterSpec and GCMParameterSpec differ from AES/GCM/NoPadding

IvParameterSpec and GCMParameterSpec differ from AES/GCM/NoPadding… here is a solution to the problem.

IvParameterSpec and GCMParameterSpec differ from AES/GCM/NoPadding

I’m using the AES/GCM/NoPadding algorithm to encrypt some data on Android (API 19 and later) before decrypting it.

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

In addition to encryption, I would like to know when I try to decrypt and use the wrong key. That’s why I prefer to use GCM as my mode to reap the benefits of verifying integrity (I believe it’s safe to assume that if there is an error in the ciphertext or key that would result in a bad decryption exception instead of garbled text).

The problem I faced was that using the above algorithm on Android API 19 and initializing the password with GCMParameterSpec I got a NoSuchAlgorithmException, and I didn’t specify any provider myself to allow Android to pick one for me that supported my algorithm. On 21+, algorithms are available.
This is how I’m initializing (similar to decryption), and the whole class is published at the end of this article.

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. Strong >

So what happens when you change these parameters? Do I still get all the same benefits of GCM?

Because the exception thrown when trying to use the wrong key is different. On API 19, a BadPaddingException is thrown when using IvParameterSpec, and on API 21+, an AEADBADTagException is thrown when GCMParameterSpec is used

Is it correct and secure to verify integrity with only IvParameterSpec and BadPaddingException at all Android API levels? I don’t want to use different implementations for different platforms, so I just want to use one.

Also, on API 21+, if I encrypt with GCMParameterSpec and then decrypt with IvParameterSpec, it decrypts! Vice versa. How effective is it?

If the above is not possible on API 19, then what can I choose as a cryptographic algorithm and policy to use (AES/CBC/PKCS5Padding with HMAC?). ) to verify the key to integrity.

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);
        }
    }
}

In addition, please feel free to suggest improvements to the provided classes and your answers, thank you.

Solution

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

That’s okay, but understand that an invalid label could mean that the tag itself has been changed, the ciphertext has been changed, the IV has been changed, the AAD has been changed, or the key is indeed incorrect.

You can also use the key checksum or something similar to check if the key size is correct before decrypting. Note, however, that the opponent can also change the check value.

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

Sure, but

GCM has been revamped to make it largely compatible, but there are still more configuration options (mostly label size) – if you need to configure it. AEADBADTagException is a BadPaddingException, so the code should work for every exception, even if 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.

Of course. Note that only tokens can throw BadPaddingException, so such exceptions do correctly identify authentication issues.

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 runs against each type of parameter specification because you specify the same tag size as the default value: 128 bits. It does not work with smaller label sizes.


Code comments:

  • charset should be a static final;
  • The key should not be passed as a byte array, but as a SecretKey instance;
  • The IV should always be 12 bytes, so the IV size does not need to be communicated;
  • If you do convey the IV size, then you need to check if it is a valid value, currently the adversary can control that byte (and let you create a large IV or throw an ArrayIndexOutOfBounds exception);
  • When handling exceptions, you need to distinguish between code problems (GCM algorithm not available) and input-related problems (size errors) – I wrote a small primer as an answer here;
  • Currently your code works well for small messages; Some kind of streaming would be fine for larger messages.

Related Problems and Solutions