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

2

8

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!

Wristwatch answered 22/2, 2016 at 16:2 Comment(1)
It is not a mistake in the code, you have to use the same IV at decryption as at encryption. Wether an IV can be sent in open depends on which effects one wants with the IV.Chromatid
O
18

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.

Osy answered 22/2, 2016 at 16:49 Comment(2)
Everything works great now. Thank you! If someone has the same problem, I used the Arrays.concatenate(...) and the Arrays.copyOfRange(...) Methods from BouncyCastle. Just make sure your second index of your range is not included in the resulting Array. That means your second Array is from index 12 until the size of the Array! Not until size - 1.Wristwatch
I'm sorry. I'm new to the site. I just published my final code. It seems to work great.Wristwatch
W
7

Here is the final version of my code which I wrote with the help of Artjom. It seems to work great but if you find any mistakes or things that weaken the security, please let me know.

import java.security.SecureRandom;
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Arrays;
import com.sun.org.apache.xml.internal.security.utils.Base64;

public class BouncyCastleEX {

private static final int iterations = 2000;
private static final int keyLength = 256;
private static final SecureRandom random = new SecureRandom();
private static BouncyCastleEX instance = null;

public String encryptString(String plaintext, String passphrase, String salt)
        throws Exception {
    return Base64.encode(encrypt(plaintext, passphrase, salt));
}

public String decryptString(String encrypted, String passphrase, String salt)
        throws Exception {
    return decrypt(Base64.decode(encrypted), passphrase, salt);
}

private BouncyCastleEX() {
    Security.addProvider(new BouncyCastleProvider());
}

public static BouncyCastleEX getInstance() {
    if (instance == null) {
        instance = new BouncyCastleEX();
    }
    return instance;
}

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

private 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,
            new IvParameterSpec(Arrays.copyOfRange(encrypted, 0, 12)),
            random);
    return new String(cipher.doFinal(Arrays.copyOfRange(encrypted, 12,
            encrypted.length)));
}

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 byte[] generateIVBytes(Cipher cipher) throws Exception {
    byte[] ivBytes = new byte[12];
    random.nextBytes(ivBytes);

    return ivBytes;
}

}
Wristwatch answered 23/2, 2016 at 8:54 Comment(1)
You should probably generate the salt randomly and write it in front of the ciphertext. Basically, the salt should be treated in the exact same way as the IV.Osy

© 2022 - 2024 — McMap. All rights reserved.