AES-GCM with BouncyCastle throws "mac check in GCM failed" when used with IV

thaasoph picture thaasoph · Feb 22, 2016 · Viewed 18.7k times · Source

I'm relatively new to developing something with encryption. Right now I'm trying to write a class which encrypts and decrypts Strings using BouncyCastle with AES-GCM. I read about the things you have to consider when implementing encryption. One of them was that you should always use a randomized IV. The problem is, everytime I try to initialize my Cipher with an IV it won't decrypt my text properly.
It just throws the following exception:

javax.crypto.AEADBadTagException: mac check in GCM failed
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$AEADGenericBlockCipher.doFinal(Unknown Source)
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(Unknown Source)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at BouncyCastleEX.decrypt(BouncyCastleEX.java:78)
at BouncyCastleEX.main(BouncyCastleEX.java:43)

I'm using the following methods to encrypt and decrypt my data.

private static final String fallbackSalt = "ajefa6tc73t6raiw7tr63wi3r7citrawcirtcdg78o2vawri7t";
private static final int iterations = 2000;
private static final int keyLength = 256;
private static final SecureRandom random = new SecureRandom();

public byte[] encrypt(String plaintext, String passphrase, String salt)
        throws Exception {
    SecretKey key = generateKey(passphrase, salt);
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
    cipher.init(Cipher.ENCRYPT_MODE, key, generateIV(cipher),random);
    return cipher.doFinal(plaintext.getBytes());
}

public String decrypt(byte[] encrypted, String passphrase, String salt)
        throws Exception {
    SecretKey key = generateKey(passphrase, salt);
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
    cipher.init(Cipher.DECRYPT_MODE, key, generateIV(cipher),random);
    return new String(cipher.doFinal(encrypted));
}

private SecretKey generateKey(String passphrase, String salt)
        throws Exception {
    PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(),
            salt.getBytes(), iterations, keyLength);
    SecretKeyFactory keyFactory = SecretKeyFactory
            .getInstance("PBEWITHSHA256AND256BITAES-CBC-BC");
    return keyFactory.generateSecret(keySpec);
}

private IvParameterSpec generateIV(Cipher cipher) throws Exception {
    byte[] ivBytes = new byte[cipher.getBlockSize()];
    random.nextBytes(ivBytes);
    return new IvParameterSpec(ivBytes);
}

If I remove the "generateIV(cipher)" from my cipher.init(...) everything works flawlessly. But as far as I know it weakens the encryption tremendously.

Right know I'm unable to figure out whether this is a small mistake in the code or something else I know nothing about.

I really appreciate your help and thanks a lot!

Answer

Artjom B. picture Artjom B. · Feb 22, 2016

You have to use the same IV for encryption and decryption. It doesn't have to be secret, but only unique for AES-GCM (it's technically a nonce). A common way is to prepend the IV to the ciphertext and remove it before decryption.

It's also common to use a message counter instead of randomly generating an IV. If the key is changed then you should reset the IV to an initial value and start counting again. At some number of messages, you need a new key, because the security guarantees of AES-GCM break down. That number is somewhere between 248 and 264 messages.