Fabs
Fabs

Reputation: 1

Java AES / GCM decryption fails

I am trying to use GCM Mode for encryption and decryption. Unfortunately decryption doesn't work.

Do I have to use the same initialization vector for both encryption and decryption classes? I already tried that, unsuccessfully...

Could the random argument in keyGen.init(128, random) be the problem?

Encryption code:

public class AES128SymmetricEncryption {

    private static final int GCM_NONCE_LENGTH = 12; // in bytes
    private static final int GCM_TAG_LENGTH = 16; // in bytes

    public static void encode (FileInputStream ciphertextSource, FileOutputStream plaintextDestination)
    {
        try {
            int numRead;
            SecureRandom random = SecureRandom.getInstanceStrong();
            KeyGenerator keyGen = KeyGenerator.getInstance("AES");
            keyGen.init(128, random);
            SecretKey key = keyGen.generateKey();
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
            GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, getIV(random));
            cipher.init(Cipher.ENCRYPT_MODE, key, spec);
            byte[] buf = new byte[2048];

            while ((numRead = ciphertextSource.read(buf)) > 0) {
                byte[] decryptedBlock = cipher.update(buf, 0, numRead);
                plaintextDestination.write(decryptedBlock);
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (plaintextDestination != null) {
                    ciphertextSource.close();
                }
                if (plaintextDestination != null) {
                    plaintextDestination.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static byte[] getIV(SecureRandom random) {

        final byte[] nonce = new byte[GCM_NONCE_LENGTH];
        random.nextBytes(nonce);
        System.out.println(nonce);
        return nonce;
    }

    public static void main(String[] args) throws GeneralSecurityException, IOException
    {
        Security.addProvider(new BouncyCastleProvider());

            FileInputStream fis = new FileInputStream("C:/Users/roehrlef/Desktop/Test Data/Source Data/100KB.jpg");
            FileOutputStream fos = new FileOutputStream("C:/Users/roehrlef/Desktop/Test Data/Encrypted Data/encrypted.jpg");
            encode(fis, fos);
    }
}

Decryption code:

public class AES128SymmetricDecryption {

    private static final int GCM_NONCE_LENGTH = 12; // in bytes
    private static final int GCM_TAG_LENGTH = 16; // in bytes

    public static void decode (FileInputStream ciphertextSource, FileOutputStream plaintextDestination)
    {
        try {
            int numRead = 0;
            SecureRandom random = SecureRandom.getInstanceStrong();
            KeyGenerator keyGen = KeyGenerator.getInstance("AES");
            keyGen.init(128, random);
            SecretKey key = keyGen.generateKey();
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
            GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, getIV(random));
            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            CipherInputStream cis = new CipherInputStream(ciphertextSource, cipher);
            byte[] buf = new byte[2048];

            while ((numRead = cis.read(buf)) > 0) {
                byte[] decryptedBlock = cipher.update(buf, 0, numRead);
                plaintextDestination.write(decryptedBlock);
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (plaintextDestination != null) {
                    ciphertextSource.close();
                }
                if (plaintextDestination != null) {
                    plaintextDestination.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static byte[] getIV(SecureRandom random) {

        final byte[] nonce = new byte[GCM_NONCE_LENGTH];
        random.nextBytes(nonce);
        System.out.println(nonce);
        return nonce;
    }

    public static void main(String[] args) throws GeneralSecurityException, IOException
    {
        Security.addProvider(new BouncyCastleProvider());

        FileInputStream fis = new FileInputStream("C:/Users/roehrlef/Desktop/Test Data/Encrypted Data/encrypted.jpg");
        FileOutputStream fos = new FileOutputStream("C:/Users/roehrlef/Desktop/Test Data/Decrypted Data/decrypted.jpg");
        decode(fis, fos);
    }
}

Upvotes: 0

Views: 1261

Answers (1)

Maarten Bodewes
Maarten Bodewes

Reputation: 93968

You're using KeyGenerator twice; once for encryption and once for decryption. This class generates a new random key. With symmetric ciphers you need to use the same key for encryption and decryption (hence the name).

In general you should use the following classes for the following purposes:

For symmetric keys (e.g. AES, HMAC):

  • KeyGenerator: brand new secret (symmetric) keys;
  • SecretKeyFactory: decoding secret (symmetric) keys, for instance generated by the method Key#getEncoded() implemented by most key classes;

And for asymmetric public / private key pairs (e.g. RSA):

  • KeyPairGenerator: brand new public / private asymmetric key pairs;
  • KeyFactory: decoding public / private (asymmetric) keys from a stored key format, for instance generated by the method Key#getEncoded() implemented by most key classes;

Both symmetric and asymmetric keys may be stored in key stores:

  • KeyStore: storing keys / certificates in a key container such as PKCS#12 key stores;

Finally there are some other options for creating keys:

  • KeyAgreement: establishing a key by a key agreement function such as Diffie-Hellman key exchange;
  • Cipher#unwrap: unwrapping (decrypting) keys created using Cipher#wrap (or a similar function on another platform) with another key.

You should probably either store and retrieve the key in a KeyStore - which you can load / save to file. Note that not all key stores are created equal; Java 9 expanded the functionality of PKCS#12 key stores and made them the default. You code also encode the key and use a SecretKeyFactory to decode it again.

Or you can just cheat and reuse the SecretKey instance you generated during encryption, and implement key storage later. That would be good for testing purposes. In the end you need to share the key for symmetric encryption.

And yes, the IV needs to be identical on both sides. Usually it is just stored in front of the ciphertext. The IV should be unique for each encryption, so you have to use the random number generator over there.

Upvotes: 1

Related Questions