Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

javax.crypto.AEADBadTagException: Tag mismatch! Error when encrypting String

I wrote a simple Encryption and Decryption helper class for my android app to encrypt and store Strings securely.

It consists of a single static public method to encrypt, then it calls a private static method to decrypt the encrypted message and returns it. I wrote the method this way to check if the message is intact after encryption/decryption.

I wrote a simple JUnit test with a String and called AssertEquals on the String before and after sending it to the Crypto encryption method.

I get this following errors from running the test:

javax.crypto.AEADBadTagException: Tag mismatch!

The error stack:

at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:571)
at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1046)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:983)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:845)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at util.Crypto.decrypt(Crypto.java:94)
at util.Crypto.encrypt(Crypto.java:64)
at com.example.ali.meappley.CryptoTest.encryptAndDecryptTest(CryptoTest.java:29)

I'm new to cryptography, but I read different stackoverflow replies and couldn't find anything of help. Some users suggested calling cipher.update(someByteArray) before calling cipher.doFinal(someByteArray) but I couldnt manage to get it working. Any suggestions?

This is my helper class

public class Crypto {

//public methods

//public static encrypt method
public static String encrypt(String messageToEncrypt, @Nullable byte[] associatedData) throws NoSuchPaddingException,
        NoSuchAlgorithmException,
        InvalidAlgorithmParameterException,
        InvalidKeyException,
        BadPaddingException,
        IllegalBlockSizeException {

    byte[] plainBytes = messageToEncrypt.getBytes();
/////////////////////////////////////////////////////////////////
    SecureRandom secureRandom = new SecureRandom();
    byte[] key = new byte[16];
    secureRandom.nextBytes(key);
    SecretKey secretKey = new SecretKeySpec(key, "AES");

    byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
    secureRandom.nextBytes(iv);

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

    if (associatedData != null) {
        cipher.updateAAD(associatedData);
    }

    byte[] cipherText = cipher.doFinal(plainBytes);

    ByteBuffer byteBuffer = ByteBuffer.allocate(4 + iv.length + cipherText.length);
    byteBuffer.putInt(iv.length);
    byteBuffer.put(iv);
    byteBuffer.put(cipherText);
    byte[] cipherMessage = byteBuffer.array();

    Arrays.fill(key,(byte) 0); //overwrite the content of key with zeros
    ///////////////////////////////////////////////////////////////////

    byte[] decrypted = decrypt(cipherMessage, null, key);

    return decrypted.toString();
}

//public static decrypt method
private static byte[] decrypt(byte[] cipherMessage, @Nullable byte[] associatedData, byte[] key) throws NoSuchPaddingException,
        NoSuchAlgorithmException,
        InvalidAlgorithmParameterException,
        InvalidKeyException,
        BadPaddingException,
        IllegalBlockSizeException {

    ByteBuffer byteBuffer = ByteBuffer.wrap(cipherMessage);
    int ivLength = byteBuffer.getInt();
    if(ivLength < 12 || ivLength >= 16) { // check input parameter
        throw new IllegalArgumentException("invalid iv length");
    }
    byte[] iv = new byte[ivLength];
    byteBuffer.get(iv);
    byte[] cipherText = new byte[byteBuffer.remaining()];
    byteBuffer.get(cipherText);

    final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(128, iv));
    if (associatedData != null) {
        cipher.updateAAD(associatedData);
    }

    cipher.update(cipherText);
    byte[] plainText= cipher.doFinal(cipherText);

    return plainText;
}
like image 733
Carlton Avatar asked Nov 15 '18 13:11

Carlton


1 Answers

There are a few issues with your code:

1) In your encrypt-method remove the following line (or shift it behind the decrypt-call).

 Arrays.fill(key, (byte) 0); // overwrite the content of key with zeros

Otherwise the key for encryption and decryption differ.

2) In your encrypt-method also pass the associatedData in your decrypt-call i.e. replace

 byte[] decrypted = decrypt(cipherMessage, null, key);

with

 byte[] decrypted = decrypt(cipherMessage, associatedData, key);

The associatedData passed for encryption and decryption have to match for validity. For the purpose of the associatedData see e.g. https://crypto.stackexchange.com/questions/6711/how-to-use-gcm-mode-and-associated-data-properly

3) In your decrypt-method remove the line

 cipher.update(cipherText);

For the purpose of the update-method see e.g. What does cipher.update do in java?

All three issues give rise to an AEADBadTagException.

4) I suspect for testing purposes your encrypt-method returns decrypted.toString() which however only gives you the object's class and hashcode. It would make more sense to return e.g. new String(decrypted).

like image 97
Topaco Avatar answered Oct 20 '22 14:10

Topaco