Search code examples
javaandroidencryptionaesaes-gcm

Error decrypting message encrypted using AES/GCM/NoPadding in Android


I'm currently using AES/GCM/NoPadding to perform cipher operations.

My encryption code:

fun encrypt(plainText: ByteArray, key: Key): ByteArray? {
        var resultText: ByteArray? = null
        try {
            val cipher = Cipher.getInstance(ALGORITHM)
            cipher.init(Cipher.ENCRYPT_MODE, key)

            val cipherText = cipher.doFinal(plainText)

            resultText = ByteBuffer.allocate(1 + cipher.iv.size + cipherText.size)
                    .put(cipher.iv.size.toByte())
                    .put(cipher.iv)
                    .put(cipherText)
                    .array()
        } catch (e : Exception) {
            Logger.e(TAG, "Error encrypting plain text", e)
        }

        return resultText
    }

My decryption code:

fun decrypt(cipherTextWithHeaders: ByteArray, key: Key): ByteArray? {
        var plainText: ByteArray? = null
        try {
            val cipher = Cipher.getInstance(ALGORITHM)

            val ivSize = cipherTextWithHeaders[0].toInt()
            val iv = ByteArray(ivSize)
            System.arraycopy(cipherTextWithHeaders, 1, iv, 0, ivSize)
            cipher.init(Cipher.DECRYPT_MODE, key, GCMParameterSpec(ivSize * 8, iv))

            val headerLen = 1 + ivSize

            val cipherText = ByteArray(cipherTextWithHeaders.size - headerLen)
            System.arraycopy(cipherTextWithHeaders, headerLen, cipherText, 0, cipherTextWithHeaders.size - headerLen)

            plainText = cipher.doFinal(cipherText)
        } catch (e : Exception) {
            Logger.e(TAG, "Error decrypting cipher text", e)
        }

        return plainText
    }

I get this exception while doing doFinal in decrypt method above:

javax.crypto.IllegalBlockSizeException
    at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:519)
    at javax.crypto.Cipher.doFinal(Cipher.java:1736)

I tried the below option during encryption:

val temp = ByteArray(12)
SecureRandom().nextBytes(temp)
cipher.init(Cipher.ENCRYPT_MODE, key, GCMParameterSpec(96, temp))

But this throws the below error:

java.security.InvalidAlgorithmParameterException: Caller-provided IV not permitted
    at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:85)
    at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:265)
    at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:148)
    at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2659)
    at javax.crypto.Cipher.tryCombinations(Cipher.java:2570)
    at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2475)
    at javax.crypto.Cipher.chooseProvider(Cipher.java:566)
    at javax.crypto.Cipher.init(Cipher.java:973)
    at javax.crypto.Cipher.init(Cipher.java:908)

Solution

  • The GCM auth tag length is not related to the IV length. The standard for AES-GCM is actually 12-bytes IV and 128 bits GCM tag, see RFC 5288, Section 3.

    Example:

    String input = "abcdef";
    
    byte[] key = new byte[16];
    (new SecureRandom()).nextBytes(key);
    
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    
    cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"));
    byte[] ciphertext = cipher.doFinal(input.getBytes());
    byte[] iv = cipher.getIV();
    GCMParameterSpec gcmspec = cipher.getParameters().getParameterSpec(GCMParameterSpec.class);
    System.out.println("ciphertext: " + ciphertext.length + ", IV: " + iv.length + ", tLen: " + gcmspec.getTLen());
    
    cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(128, iv));
    byte[] plaintext = cipher.doFinal(ciphertext);
    
    System.out.println("plaintext : " + new String(plaintext));
    

    Prints:

    ciphertext: 22, IV: 12, tLen: 128
    plaintext : abcdef
    

    Try changing GCMParameterSpec(ivSize * 8, iv) to GCMParameterSpec(128, iv).

    Though the issue could also be outside, i.e. the ciphertext could be badly encoded or cut off somewhere. Check cipherText.length.

    java.security.InvalidAlgorithmParameterException: Caller-provided IV not permitted
    

    That is a limitation of the Android crypto implementation; it wants to generate the IV itself during encryption.