Search code examples
javacryptographyaesbouncycastleaes-gcm

AEADBadTagException doesn't throw when tampering with AES encrypted buffer in AEAD GCM mode


Java: Oracle jre1.8.0_45

Provider: BC, BouncyCastle v1.52

Cipher: AES 256bit keys (security policy installed)

AEAD Mode: GCM

Algo: AES/GCM/NoPadding

I have a perfectly working AES encryption/decryption with the parameters shown above. Then during my debug I added a simple TAMPERING simulation by changing data in the Encrypted buffer before decrypting. I expected an AEADBadTagException to be thrown but it didn't happen. I did NOT use any upedateAAD() yet, we are talking about pure ciphered data pay-load.

I do that tampering simply like this, I overwrite the first byte of the byte[] after it already contains the encrypted data and the 16 extra bytes of the authentication tag.

// set-up for encryption, key, IV, etc...
...
try
{
  String sPlainText="The non-encrypted (AES) message.";    
  byte[] baEncrypted=oCIPH.doFinal(sPlainText.getBytes());

  MetaLogbook.info(baEncrypted); // Shows well encrypted buffer

  // Tampering simulations
  baEncrypted[0]=0x67;

  // re-initialize for decryption, same key and IV...

  String sDecryptedText=new String(oCIPH.doFinal(baEncrypted),"UTF-8");
  MetaLogbook.info(sDecryptedText);

  // The above log line shows the plain text with a different first letter
  // each time that i change 0x67 in other values. The rest of the message
  // matches the plain text on input. I can see the 16 extra bytes of the 
  // authentication tag appended to the clear text.
}
catch(Exception e)
{
  // I expected to come here due to a AEADBadTagException but I never 
  // come here.
  MetaLogbook.error(e);
}

The resulting decrypted text changes at the first character as i change the value that i assign while simulating the tampering. It changes in a non-linear way. While 0x65 produces a 'c', 0x67 produces a '?' and so on. The rest of the plain message remains correct, only the first character of the decrypted output seems affected.

I understood from the standard Java 8 doc of the Cipher class that in AEAD GCM mode the authentication tag is created at encryption time (and it was because I see it in my encrypted output byte[] appended at the end) and verified at decryption time (and I provided the full output of encryption including the 16 bytes tag as decryption input) and if the tag would not verify with that data (including AAD data that i don't use now but will) it would throw a AEADBadTagException. In my code it doesn't do that.

I tried this with data that is a multiple of 16 bytes as well as with data that is not. The result is the same for both. If using the same tampering (0x67) value that first letter in the plain text output changes if the message gets longer but that makes sense. The mentioned erroneous first character 'c' becomes a '6' if i add some bytes to the message so that it is not a multiple of 16. In the used AES/GCM/NoPadding the length must not be a multiple of 16 anyway.

Is this a mis-understanding of the documentation, is there some other method that needs to be called to 'enable' this throw behaviour (I could find any), or doesn't BounceyCastle throw it (I understood that a provider needs to implement the crypto classes ISP such that everything behaves as described in the Java 8 Docs Cipher class.

I could not compare with the SunJCE provider because it doesn't support AES/GCM/NoPadding.

Does anyone has some extra info. TIA

UPDATE 29/AUG: Added code to show that identical code throws with SunJCE and not with BC provider as part of discussion in comments.

private static void testing()
{
  try
  {
    // Unremark these lines to see it work
    //Security.addProvider(new BouncyCastleProvider()); // "BC"
    //Cipher oCIPH=Cipher.getInstance("AES/GCM/NoPadding", "BC");

    // Unremark these lines to see it fail
    Cipher oCIPH=Cipher.getInstance("AES/GCM/NoPadding", "SunJCE"); 

    // Make a quick and dirty IV and Symmetric Key
    byte[] baIV="EECE34808EF2A9AC".getBytes("UTF-8");
    byte[] baKey="010F05E3E0104EB59D10F37EA8D4BB6B".getBytes("UTF-8");

    // Make IV and Key (well KeySpec for AES) object. Use IV parspec because
    // defaults to 128bit Authentication tag size & works in both GCM & CBC.
    IvParameterSpec ps=new IvParameterSpec(baIV);
    SecretKeySpec sk=new SecretKeySpec(baKey,"AES");

    // Unremakr one line, either shrtline (multiple of 16 bytes) or long line   
    //String sPlainText="The non-encrypted (AES) message.";
    String sPlainText="The non-encrypted (AES) message. Everything after the . makes this NOT a multiple of 16 bytes.";

    // Encrypt
    oCIPH.init(Cipher.ENCRYPT_MODE, sk, ps);
    byte[] baEncrypted=oCIPH.doFinal(sPlainText.getBytes());

    // Decrypt
    oCIPH.init(Cipher.DECRYPT_MODE, sk, ps);
    String sDecryptedText=new String(oCIPH.doFinal(baEncrypted),"UTF-8");        
  }
  catch(Exception e)
  {
    MetaLogbook.log("Security Tools Exception",e);
  }
} 

The code above can be run with SunJCE or with BouncyCastle by unremarking the the wanted lines at the top. In BC these code runs and does what it is expected to do. With SunJCE provider unremarked an error is thrown:

class java.security.InvalidAlgorithmParameterException: Unsupported parameter: javax.crypto.spec.IvParameterSpec@4fccd51b com.sun.crypto.provider.CipherCore.init (CipherCore.java:509) com.sun.crypto.provider.AESCipher.engineInit (AESCipher.java:339) javax.crypto.Cipher.init (Cipher.java:1394) javax.crypto.Cipher.init (Cipher.java:1327)


Solution

  • The original post had two issues under discussion. One has been solved (the AEADBadTagException) the other remains pending (see UPDATE 29/AUG in original post).

    The solved problem: I had recently to write exception code for the usages of the Key/SecretKey Class with AES to use SecretKeySpec instead. The change induced an error which influence the path the code followed and was corrected now in the context of searching for the non throwing of the AEADBadTagException. The fact that all the rest kept working was because the flow change resulted to initialize both times for Encryption instead for Decryption the second time. What I don't understand is that decryption worked correctly anyway. AES is a symmetric algorithm but it has an S-Box and Reverse S-Box and one would think that therefore encryption cannot just be used in stead of decryption as in full symmetric ciphers such as DES.

    The second issue problem remains pending:

    class java.security.InvalidAlgorithmParameterException: Unsupported parameter: javax.crypto.spec.IvParameterSpec@4fccd51b

    It can be reproduced by the provided code by just selecting the providers at the top of the code and leave all the rest unchanged. The code works with BC, not with SunJCE.

    I see there is a Metalogbook line that may have to be changed in your own logging code.

    Although the SunJCE provider isn't one I use for the Ciphering and the GCM problem is solved as far as i am concerned I'll keep following this question to supply more information for the SunJCE throw if needed.

    UPDATE: By doing some further digging I found the problem of the IVParamSpec throw. BC accepts this object for both CBC and GCM and defaults the authentication tag to 128bit. The SunJCE on the other hand requires specifically a GCMParamSpec object and an explicit size for the authentication tag for GCM and accepts the IVParamSpec for CBC but not for GCM.