3

I have a problem with Java AES GCM encryption. I have followed the instructions on this page to generate the following code:

package aes;

import java.nio.ByteBuffer;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AES {
    private Cipher encryptCipher;
    private Cipher decryptCipher;
    private byte[] key;
    private int keyLength;
    private SecretKeySpec keySpec;
    private SecureRandom random;

    public AES(String key) {
        try {
            this.key = key.getBytes();
            this.keyLength = this.key.length*8; // key length in bits
            this.keySpec = new SecretKeySpec(this.key, "AES");
            this.encryptCipher = Cipher.getInstance("AES/GCM/NoPadding");
            this.decryptCipher = Cipher.getInstance("AES/GCM/NoPadding");
            this.random = SecureRandom.getInstance("SHA1PRNG");
        }
        catch (Exception e) {
            System.err.println(e.getMessage());
        }
    }
    public byte[] encrypt(byte[] plaintext) {
        try {
            byte[] iv = new byte[12]; // create new IV
            random.nextBytes(iv); 
            encryptCipher.init(Cipher.ENCRYPT_MODE, keySpec, new GCMParameterSpec(keyLength, iv));
            byte[] encrypted = encryptCipher.doFinal(plaintext);
            ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + encrypted.length);
            byteBuffer.put(iv);
            byteBuffer.put(encrypted);
            return byteBuffer.array(); // IV(0)...IV(11) + ENCRYPTED(0)...ENCRYPTED(N)
        }
        catch (Exception e) {
            System.err.println(e.getMessage());
            e.printStackTrace();
            return null;
        }
    }
    public byte[] decrypt(byte[] ciphertext) {
        try {
            ByteBuffer byteBuffer = ByteBuffer.wrap(ciphertext);
            byte[] iv = new byte[12];
            byteBuffer.get(iv);
            byte[] encrypted = new byte[byteBuffer.remaining()];
            byteBuffer.get(encrypted);
            decryptCipher.init(Cipher.DECRYPT_MODE, keySpec, new GCMParameterSpec(keyLength, iv));
            return decryptCipher.doFinal(ciphertext);
        }
        catch (Exception e) {
            System.err.println(e.getMessage());
            e.printStackTrace();
            return null;
        }
    } 
}

In the main, I call the whole thing as follows:

package aes;

public class Main {
    static byte[] plaintext = new byte[] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53};
    public static void main(String[] args) {
        AES aes = new AES("Random09Random09");

        byte[] encrypted = aes.encrypt(plaintext);
        byte[] decrypted = aes.decrypt(encrypted);  
    }
}

Now I always get a tag mismatch error like this:

Tag mismatch!
javax.crypto.AEADBadTagException: Tag mismatch!
    at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:578)
    at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1049)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:985)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:847)
    at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
    at javax.crypto.Cipher.doFinal(Cipher.java:2164)
    at aes.AES.decrypt(AES.java:55)
    at aes.Main.main(Main.java:9)

for which I can not determine the cause. The StackTrace is not helpful to me. I would be grateful if someone could help me with that. As you can see in the StackTrace, the error is in decryption in step

doFinal (...)
Lukas Nothhelfer
  • 261
  • 1
  • 5
  • 14
  • @kelalaka Is this a solution? To my knowledge, Base64 is just an encoding to get printable characters. How should this be related to the tag problem? – Lukas Nothhelfer Dec 17 '18 at 12:15
  • As mentioned in the question I have the essential part of the code from a website. There, the whole seems to work. I do not understand why this is not the case with me – Lukas Nothhelfer Dec 17 '18 at 12:16
  • ahh, The GCM mode requires the tag. cipher.updateAAD(aad); this is missing in the encryption. see [this](https://gist.github.com/praseodym/f2499b3e14d872fe5b4a) line 48. Do the results are same? – kelalaka Dec 17 '18 at 12:21
  • @kelalaka Thanks for your efforts and the link you posted. That was not it. Nevertheless, I found the mistake. I'll create an answer soon. – Lukas Nothhelfer Dec 17 '18 at 12:40
  • 1
    Unrelated : you might want to to make sure `this.key = key.getBytes();` fits your need. `String#getBytes` is a system dependant method (relies on your host system's default encoding scheme, usually UTF8 on linux, but YMMV on Windows, other *nix, MacOS, or if you launch the JVM with ad-hoc environment variables or `-Dfile.encoding`...) You might want to check where your passwords come from (russian vs. chinese vs. latin alphabets ?) and how Strings may be converted to bytes, by taking into account your system as a whole (external programs). – GPI Dec 17 '18 at 13:14
  • @GPI Many thanks for the answer. It is not a public system, but it is encapsulated and the OS and the whole stuff are fixed and predetermined. – Lukas Nothhelfer Dec 17 '18 at 13:20

1 Answers1

9

I finally found the mistake by myself. The mistake was that in the decrypt method I tried to do doFinal (...) on the whole received message and not just on the extracted ciphertext. I left the mistake in my question and post here the relevant (now correct) part of the program. @kelalaka Thank you for your efforts.

    public byte[] decrypt(byte[] ciphertext) {
        try {
            ByteBuffer byteBuffer = ByteBuffer.wrap(ciphertext);
            byte[] iv = new byte[12];
            byteBuffer.get(iv);
            byte[] encrypted = new byte[byteBuffer.remaining()];
            byteBuffer.get(encrypted);
            decryptCipher.init(Cipher.DECRYPT_MODE, keySpec, new GCMParameterSpec(keyLength, iv));
            return decryptCipher.doFinal(encrypted); // here was the mistake
        }
        catch (Exception e) {
            System.err.println(e.getMessage());
            e.printStackTrace();
            return null;
        }
    } 

And the main:

package aes;

public class Main {
    static byte[] plaintext = new byte[] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53};
    public static void main(String[] args) {
        AES aes = new AES("Random09Random09");

        byte[] encrypted = aes.encrypt(plaintext);
        byte[] decrypted = aes.decrypt(encrypted);  
        System.out.println("Original:\n" + new String(plaintext) + "\nEncrypted:\n" + new String(encrypted) + "\nDecrypted:\n" + new String(decrypted));
    }
}

The (now correct) output of the program:

Original:
ABCDEFGHIJKLMNOPQRS
Encrypted:
�~q꽕kl�9���&�ZB=�WPU�"�'�H���]:?Bo
Decrypted:
ABCDEFGHIJKLMNOPQRS
Lukas Nothhelfer
  • 261
  • 1
  • 5
  • 14
  • Looks good other than SHA1PRNG and the key generation. Plus `SecretKey` and `SecretKeySpec` should be cleared after use... – Saptarshi Basu Dec 17 '18 at 13:10
  • @SaptarshiBasu This question refers to my other question, which can be seen here[link](https://stackoverflow.com/questions/53706907/extremely-slow-built-in-aes-encryption-with-java). I'm going to try AES / GCM now and probably have to take SHA1PRNG, although that's still very slow for my application. The Java program will eventually run on a Beagle Bone board. There are limited CPU resources and I still hope to find a faster way than SHA1PRNG. – Lukas Nothhelfer Dec 17 '18 at 13:17