xross
xross

Reputation: 648

javax.crypto.AEADBadTagException: Tag mismatch for AES/GCM/No Padding encryptor/decryptor

I have been trying to figure this out for days now. Encryption method works fine, but during the decryption tests I am getting the exception below. Especially I am using: AES/GCM/NoPadding . As far as I know T_LEN should be IV_LENGTH*8 as a byte array representation. The error truly shows at ExampleCryptografer.java decryption method: byte[] decryptedText = cipher.doFinal(decoded);

javax.crypto.AEADBadTagException: Tag mismatch!

at java.base/com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:623)
at java.base/com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1116)
at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1053)
at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202)
at com.example.ExampleCryptografer.decrypt(ExampleCryptografer.java:61)
at com.example.ExampleCryptograferTest.decrypt_givenEncryptedExample_ShouldSucceed(ExampleCryptograferTest.java:21)

This is how my tests looks like:

public class ExampleCryptographerTest {

private ExampleCryptographer objectUnderTest = new ExampleCryptographer("knownKeyForTest=");

@Test
public void decrypt_givenEncryptedExample_ShouldSucceed() {
    String example = "afasfdafafa=";
    String encodedExample = objectUnderTest.encrypt(example);

    String result = objectUnderTest.decrypt(encodedExample);

    assertThat(result).isNotNull();
    assertThat(result.length()).isEqualTo(48);
}

@Test
public void encrypt_givenExample_ShouldSucceed() {
    String example = "afasfdafafa=";

    String result = objectUnderTest.encrypt(example);

    assertThat(result).isNotNull();
    assertThat(result.length()).isEqualTo(48);
}

@Test
public void decrypt_givenEncryptedExampleWithOtherKey_ShouldFail() {
    String example = "afasfdafafa=";
    String encodedExample = new ExampleCryptographer("otherKeyForTest=").encrypt(example);

    Throwable throwable = catchThrowable(() -> objectUnderTest.decrypt(encodedExample));

    assertThat(throwable)
        .isInstanceOf(IllegalArgumentException.class);
}

@Test(expected = InvalidKeyException.class)
public void encrypt_givenInvalidKey_ShouldFail() {
    new ExampleCryptographer("invalid").encrypt("test");
}

}

and finally the actual code:

public class ExampleCryptographer {

private static final String ALGORITHM = "AES";

private final Key key;
private static final int T_LEN = 96;
private static final int IV_LENGTH = 12;
private final Base64 base64 = new Base64(76, null, true);

@SneakyThrows
public ExampleCryptographer(@Value("${myKey}") String secretKey) {
    this.key = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);
}

@SneakyThrows
public String encrypt(@NonNull String text) {
    byte[] iv = new byte[IV_LENGTH];
    (new SecureRandom()).nextBytes(iv);

    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    GCMParameterSpec ivSpec = new GCMParameterSpec(T_LEN, iv);
    cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);

    byte[] ciphertext = cipher.doFinal(text.getBytes(UTF_8));
    byte[] encrypted = new byte[iv.length + ciphertext.length];
    System.arraycopy(iv, 0, encrypted, 0, iv.length);
    System.arraycopy(ciphertext, 0, encrypted, iv.length, ciphertext.length);

    return base64.encodeAsString(encrypted);
}

@SneakyThrows
public String decrypt(@NonNull String encryptedText) {
    byte[] decoded = base64.decode(encryptedText);

    byte[] iv = Arrays.copyOfRange(decoded, 0, IV_LENGTH);

    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    GCMParameterSpec ivSpec = new GCMParameterSpec(T_LEN, iv);
    cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

    byte[] decryptedText = cipher.doFinal(decoded);

    return new String(decryptedText);
}

}

Can anyone help? I have been reading many about, and still cannot find anything wrong.

Upvotes: 9

Views: 27949

Answers (1)

dave_thompson_085
dave_thompson_085

Reputation: 38821

T_LEN is the size OF THE AUTHENTICATION TAG in bits. It should be large enough that the risk of (successful) forgery does not exceed that acceptable to your data owners, but is not related to the IV in any way. If you don't have an analysis that less is sufficient, and you aren't in a resource-constrained environment (and JavaSE never is), just use the max of 128.

Your main problem is in encrypt you reasonably concatenate IV+ciphertext (which for Java includes the tag), but in decrypt you use the first bytes as the IV and the whole buffer as the ciphertext when it should be Arrays.copyOfRange(decoded,IV_LENGTH,decoded.length).

Also, AES key must be exactly 16, 24, or 32 bytes and should be random bits, which cannot be reliably represented directly in a Java String. Generally you should use byte[] and if you need to pass or store it as a string encode to (and decode from) hex or base64.

Finally, on encrypt you encode with getBytes() as UTF-8, but on decrypt you decode with new String using the default encoding, which varies from one JVM to another and often depends on the environment and often isn't UTF-8 in which case this may return 'mojibake' (effectively garbage) not the data you encrypted.

Oh, and AEADBadTagException is not a subclass of IllegalArgumentException.

Upvotes: 13

Related Questions