Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android AES/GCM/NoPadding Bug on certain length of input bytes?

I have written a class which is used to de-and encrypt arbitrary data.

It's working like this: Because an AndroidKeyStore generated AES key is "lost" after i.e. uninstalling the app, we have a public/private key pair, where the public key is included in the app. For fail save purposes, it's used to encrypt the AES Key, which is padded in front of each encrypted message along the IV. This way we're able to recover the AES key with our Private Key

In a unit test I found out that it works, for MOST inputs. Strangely on certain byte array length it fails (I found i.e. 81920, 131073.)

So here is the AESCrypto code:

package com.mycompany.appname.crypto;

import android.content.Context;
import android.content.SharedPreferences;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.support.annotation.NonNull;
import android.util.Base64;
import android.util.Log;

import com.mycompany.appname.R;

import java.io.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AesGcmCrypto implements Crypto{

    public static final String TAG = AesGcmCrypto.class.getSimpleName();

    private static final String KEY_ALIAS = "OI1lTI1ITLI1l0";

    private static final String PREF_NAME = "CryptoPrefs";
    private static final String KEY_ENCRYPTED_SECRET = "encryptedSecret";

    private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
    private static final String PUBLIC_KEY_ASSET = "public_key.der";

    private static final int  IV_SIZE = 12;

    private static final String AES = KeyProperties.KEY_ALGORITHM_AES;
    private static final String AES_BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM;
    private static final String AES_PADDING = KeyProperties.ENCRYPTION_PADDING_NONE;
    private static final String AES_MODE = AES + "/" + AES_BLOCK_MODE + "/" + AES_PADDING;
    //generate key, possible values  128 bit key (16), 192(24), 256(32)
    private static final int    AES_KEY_SIZE = 32;
    private static final int    AES_TAG_LEN = 128;

    private static final String RSA = KeyProperties.KEY_ALGORITHM_RSA;
    private static final String RSA_BLOCK_MODE =  KeyProperties.BLOCK_MODE_ECB;
    private static final String RSA_PADDING = KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1;
    private static final String RSA_MODE = RSA + "/" + RSA_BLOCK_MODE + "/" + RSA_PADDING;
    private static final String RSA_PROVIDER = "AndroidOpenSSL";


    private final Context mContext;
    private final SharedPreferences mPrefs;

    private SecureRandom mSecureRandom;
    private KeyStore mAndroidKeyStore;
    private PublicKey mPublicKey;
    private byte[] mEncryptedSecretKey;

    public AesGcmCrypto(Context context) {
        mContext = context;
        mSecureRandom = new SecureRandom();
        mPrefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
        try {
            mAndroidKeyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
            mAndroidKeyStore.load(null);

        } catch (KeyStoreException e) {
            Log.wtf(TAG, mContext.getString(R.string.err_keystore_not_avail), e);
        } catch (Exception e) {
            Log.wtf(TAG, mContext.getString(R.string.err_keystore_not_loadable), e);
        }
    }

    void reset() throws KeyStoreException {
        mAndroidKeyStore.deleteEntry(KEY_ALIAS);
    }

    @Override
    public byte[] encrypt(byte[] message) throws GeneralSecurityException{
        Cipher cipher = Cipher.getInstance(AES_MODE);
        cipher.init(Cipher.ENCRYPT_MODE, getSecretKey());
        GCMParameterSpec parameterSpec = cipher.getParameters().getParameterSpec(GCMParameterSpec.class);
        byte[] cryptedBytes = cipher.doFinal(message);
        byte[] iv = parameterSpec.getIV();
        byte[] encryptedSecretKey = getEncryptedSecretKey();
        return ByteBuffer.allocate(iv.length + encryptedSecretKey.length + cryptedBytes.length)
                .put(iv)
                .put(encryptedSecretKey)
                .put(cryptedBytes)
                .array();
    }

    @Override
    public byte[] decrypt(byte[] bytes) throws GeneralSecurityException{
        ByteBuffer buffer = ByteBuffer.wrap(bytes);
        byte[] iv = new byte[IV_SIZE];
        buffer.get(iv);

        //skip aes key bytes
        byte[] irrelevant = new byte[AES_KEY_SIZE * 8];
        buffer.get(irrelevant);

        byte[] encryptedMessage = new byte[bytes.length - IV_SIZE - irrelevant.length];
        buffer.get(encryptedMessage);

        Cipher cipher = Cipher.getInstance(AES_MODE);
        GCMParameterSpec parameterSpec = new GCMParameterSpec(AES_TAG_LEN, iv);
        cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), parameterSpec);
        byte[] decryptedMessage = cipher.doFinal(encryptedMessage);
        return decryptedMessage;
    }

    public PublicKey getPublicKey() {
        if (null == mPublicKey) {
            mPublicKey = readPublicKey();
        }
        return mPublicKey;
    }

    public byte[] getEncryptedSecretKey() {
        if (null == mEncryptedSecretKey){
            mEncryptedSecretKey = Base64.decode(mPrefs.getString(KEY_ENCRYPTED_SECRET, null), Base64.NO_WRAP);
        }
        return mEncryptedSecretKey;
    }

    private void saveEncryptedSecretKey(byte[] encryptedSecretKey){
        String base64EncryptedKey = Base64.encodeToString(encryptedSecretKey, Base64.NO_WRAP);
        mPrefs.edit().putString(KEY_ENCRYPTED_SECRET, base64EncryptedKey).apply();
    }

    protected SecretKey getSecretKey(){
        SecretKey secretKey = null;
        try {
            if (!mAndroidKeyStore.containsAlias(KEY_ALIAS)){
               generateAndStoreSecureKey();
            }
            secretKey = (SecretKey) mAndroidKeyStore.getKey(KEY_ALIAS, null);
        } catch (KeyStoreException e) {
            Log.wtf(TAG, mContext.getString(R.string.err_keystore_aliasing_forbidden), e);
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        }
        return secretKey;
    }

    private void generateAndStoreSecureKey()
            throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, KeyStoreException, BadPaddingException, IllegalBlockSizeException {
        //We cannot use AndroidKeyStore Generator, because it keeps its key-bytes inside the secure element
        SecretKey secretKey = generateSecureRandomKey();
        PublicKey publicKey = getPublicKey();
        Cipher keyCipher = Cipher.getInstance(RSA_MODE, RSA_PROVIDER);
        keyCipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encryptedSecretKeyBytes = keyCipher.doFinal(secretKey.getEncoded());

        saveEncryptedSecretKey(encryptedSecretKeyBytes);

        KeyProtection keyProtection = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_VERIFY)
                .setBlockModes(AES_BLOCK_MODE)
                .setEncryptionPaddings(AES_PADDING)
                .build();
        mAndroidKeyStore.setEntry(KEY_ALIAS, new KeyStore.SecretKeyEntry(secretKey), keyProtection);
    }


    protected PublicKey readPublicKey() {
        DataInputStream dis = null;
        PublicKey key = null;
        try {
            dis = new DataInputStream(mContext.getResources().getAssets().open(PUBLIC_KEY_ASSET));
            byte[] keyBytes = new byte[dis.available()];
            dis.readFully(keyBytes);

            X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
            KeyFactory facotory = KeyFactory.getInstance(RSA);
            key = facotory.generatePublic(spec);
        } catch (Exception e) {
            key = null;
        } finally {
            if (null != dis) {
                try {
                    dis.close();
                } catch (IOException e) {
                    Log.wtf(TAG, mContext.getString(R.string.err_cannot_close), e);
                }
            }
        }
        return key;
    }

    @NonNull
    protected SecretKey generateSecureRandomKey() {
        return new SecretKeySpec(generateSecureRandomBytes(AES_KEY_SIZE), AES);
    }

    @NonNull
    protected byte[] generateSecureRandomBytes(int byteCount) {
        byte[] keyBytes = new byte[byteCount];
        mSecureRandom.nextBytes(keyBytes);
        return keyBytes;
    }
}

The unit test function looks like this:

 @Test
public void testCrypto() throws Exception{

    AesGcmCrypto aesGcmCrypto = new AesGcmCrypto(InstrumentationRegistry.getTargetContext());
    aesGcmCrypto.reset();
    Random rand = new Random();
    byte[] buffer = null;
    for (int i = 1024 *20; i < 1024 * 200; i += 1024){
        Log.i(TAG, "Testing " + i);
        buffer = new byte[i];
        rand.nextBytes(buffer);

        byte[] encrypt = aesGcmCrypto.encrypt(buffer);
        Assert.assertNotNull(encrypt);
        decrypt = aesGcmCrypto.decrypt(encrypt);
        Assert.assertNotNull(decrypt);
        Assert.assertTrue(Arrays.equals(buffer, decrypt));

    }

}

which logs:

    09-13 00:06:16.034 17222-17238/com.mycompany.appname I/TestRunner: started: testCrypto(com.mycompany.appname.crypto.CryptoTest)
09-13 00:06:16.052 17222-17238/com.mycompany.appname I/CryptoTest: Testing 20480
09-13 00:06:16.205 17222-17238/com.mycompany.appname I/CryptoTest: Testing 21504
09-13 00:06:16.269 17222-17238/com.mycompany.appname I/CryptoTest: Testing 22528
09-13 00:06:16.337 17222-17238/com.mycompany.appname I/CryptoTest: Testing 23552
09-13 00:06:16.406 17222-17238/com.mycompany.appname I/CryptoTest: Testing 24576
09-13 00:06:16.469 17222-17238/com.mycompany.appname I/CryptoTest: Testing 25600
09-13 00:06:16.551 17222-17238/com.mycompany.appname I/CryptoTest: Testing 26624
09-13 00:06:16.632 17222-17238/com.mycompany.appname I/CryptoTest: Testing 27648
09-13 00:06:16.700 17222-17238/com.mycompany.appname I/CryptoTest: Testing 28672
09-13 00:06:16.765 17222-17238/com.mycompany.appname I/CryptoTest: Testing 29696
09-13 00:06:16.839 17222-17238/com.mycompany.appname I/CryptoTest: Testing 30720
09-13 00:06:16.906 17222-17238/com.mycompany.appname I/CryptoTest: Testing 31744
09-13 00:06:16.973 17222-17238/com.mycompany.appname I/CryptoTest: Testing 32768
09-13 00:06:17.043 17222-17238/com.mycompany.appname I/CryptoTest: Testing 33792
09-13 00:06:17.118 17222-17238/com.mycompany.appname I/CryptoTest: Testing 34816
09-13 00:06:17.191 17222-17238/com.mycompany.appname I/CryptoTest: Testing 35840
09-13 00:06:17.266 17222-17238/com.mycompany.appname I/CryptoTest: Testing 36864
09-13 00:06:17.341 17222-17238/com.mycompany.appname I/CryptoTest: Testing 37888
09-13 00:06:17.768 17222-17238/com.mycompany.appname I/CryptoTest: Testing 38912
09-13 00:06:17.850 17222-17238/com.mycompany.appname I/CryptoTest: Testing 39936
09-13 00:06:17.938 17222-17238/com.mycompany.appname I/CryptoTest: Testing 40960
09-13 00:06:18.020 17222-17238/com.mycompany.appname I/CryptoTest: Testing 41984
09-13 00:06:18.098 17222-17238/com.mycompany.appname I/CryptoTest: Testing 43008
09-13 00:06:18.171 17222-17238/com.mycompany.appname I/CryptoTest: Testing 44032
09-13 00:06:18.245 17222-17238/com.mycompany.appname I/CryptoTest: Testing 45056
09-13 00:06:18.672 17222-17238/com.mycompany.appname I/CryptoTest: Testing 46080
09-13 00:06:18.758 17222-17238/com.mycompany.appname I/CryptoTest: Testing 47104
09-13 00:06:18.838 17222-17238/com.mycompany.appname I/CryptoTest: Testing 48128
09-13 00:06:18.914 17222-17238/com.mycompany.appname I/CryptoTest: Testing 49152
09-13 00:06:18.992 17222-17238/com.mycompany.appname I/CryptoTest: Testing 50176
09-13 00:06:19.283 17222-17238/com.mycompany.appname I/CryptoTest: Testing 51200
09-13 00:06:19.434 17222-17238/com.mycompany.appname I/CryptoTest: Testing 52224
09-13 00:06:19.609 17222-17238/com.mycompany.appname I/CryptoTest: Testing 53248
09-13 00:06:19.722 17222-17238/com.mycompany.appname I/CryptoTest: Testing 54272
09-13 00:06:19.832 17222-17238/com.mycompany.appname I/CryptoTest: Testing 55296
09-13 00:06:20.021 17222-17238/com.mycompany.appname I/CryptoTest: Testing 56320
09-13 00:06:20.171 17222-17238/com.mycompany.appname I/CryptoTest: Testing 57344
09-13 00:06:20.335 17222-17238/com.mycompany.appname I/CryptoTest: Testing 58368
09-13 00:06:20.477 17222-17238/com.mycompany.appname I/CryptoTest: Testing 59392
09-13 00:06:20.658 17222-17238/com.mycompany.appname I/CryptoTest: Testing 60416
09-13 00:06:20.812 17222-17238/com.mycompany.appname I/CryptoTest: Testing 61440
09-13 00:06:21.001 17222-17238/com.mycompany.appname I/CryptoTest: Testing 62464
09-13 00:06:21.108 17222-17238/com.mycompany.appname I/CryptoTest: Testing 63488
09-13 00:06:21.267 17222-17238/com.mycompany.appname I/CryptoTest: Testing 64512
09-13 00:06:21.414 17222-17238/com.mycompany.appname I/CryptoTest: Testing 65536
09-13 00:06:21.570 17222-17238/com.mycompany.appname I/CryptoTest: Testing 66560
09-13 00:06:21.731 17222-17238/com.mycompany.appname I/CryptoTest: Testing 67584
09-13 00:06:21.902 17222-17238/com.mycompany.appname I/CryptoTest: Testing 68608
09-13 00:06:22.083 17222-17238/com.mycompany.appname I/CryptoTest: Testing 69632
09-13 00:06:22.255 17222-17238/com.mycompany.appname I/CryptoTest: Testing 70656
09-13 00:06:22.478 17222-17238/com.mycompany.appname I/CryptoTest: Testing 71680
09-13 00:06:22.638 17222-17238/com.mycompany.appname I/CryptoTest: Testing 72704
09-13 00:06:22.840 17222-17238/com.mycompany.appname I/CryptoTest: Testing 73728
09-13 00:06:23.146 17222-17238/com.mycompany.appname I/CryptoTest: Testing 74752
09-13 00:06:23.345 17222-17238/com.mycompany.appname I/CryptoTest: Testing 75776
09-13 00:06:23.647 17222-17238/com.mycompany.appname I/CryptoTest: Testing 76800
09-13 00:06:23.820 17222-17238/com.mycompany.appname I/CryptoTest: Testing 77824
09-13 00:06:23.995 17222-17238/com.mycompany.appname I/CryptoTest: Testing 78848
09-13 00:06:24.200 17222-17238/com.mycompany.appname I/CryptoTest: Testing 79872
09-13 00:06:24.394 17222-17238/com.mycompany.appname I/CryptoTest: Testing 80896
09-13 00:06:24.645 17222-17238/com.mycompany.appname I/CryptoTest: Testing 81920
09-13 00:06:24.849 17222-17238/com.mycompany.appname I/TestRunner: failed: testCrypto3(com.mycompany.appname.crypto.CryptoTest)
    ----- begin exception -----
09-13 00:06:24.858 17222-17238/com.mycompany.appname I/TestRunner: junit.framework.AssertionFailedError
        at junit.framework.Assert.fail(Assert.java:48)
        at junit.framework.Assert.assertTrue(Assert.java:20)
        at junit.framework.Assert.assertTrue(Assert.java:27)
        at com.mycompany.appname.crypto.CryptoTest.testCrypto3(CryptoTest.java:72)
        at java.lang.reflect.Method.invoke(Native Method)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
        at android.support.test.runner.AndroidJUnit4.run(AndroidJUnit4.java:101)
        at org.junit.runners.Suite.runChild(Suite.java:128)
        at org.junit.runners.Suite.runChild(Suite.java:27)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
        at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
        at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:384)
        at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2145)
    ----- end exception -----
09-13 00:06:24.875 17222-17238/com.mycompany.appname I/TestRunner:   finished: testCrypto(com.mycompany.appname.crypto.CryptoTest)

The big question now is: Is there an error in my implementation, or is it a framework error, which should get reported, to prevent others from not being able to de- and encrypt data of a certain byte length?

BTW: I fixed the problem using a buffered de and encrypt like this (exchange method in AesGcmCrypto)

private static final int CIPHER_CHUCK_SIZE = 64 * 1024;
@Override
public byte[] encrypt(byte[] message) throws GeneralSecurityException {
    Cipher cipher = Cipher.getInstance(AES_MODE);
    cipher.init(Cipher.ENCRYPT_MODE, getSecretKey());
    GCMParameterSpec parameterSpec = cipher.getParameters().getParameterSpec(GCMParameterSpec.class);
    int outLen = cipher.getOutputSize(message.length);

    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    try {
        for (int i = 0; i < message.length; i += CIPHER_CHUCK_SIZE) {
            int len = Math.min(CIPHER_CHUCK_SIZE, message.length - i);
            bout.write(cipher.update(message, i, len));
        }
        bout.write(cipher.doFinal());
    } catch (IOException e) {
        e.printStackTrace();
        throw new GeneralSecurityException(e);
    }

    byte[] cryptedBytes = bout.toByteArray();
    if (outLen != cryptedBytes.length)
        throw new GeneralSecurityException("cryptedBytes too small");

    byte[] iv = parameterSpec.getIV();
    byte[] encryptedSecretKey = getEncryptedSecretKey();
    return ByteBuffer.allocate(iv.length + encryptedSecretKey.length + cryptedBytes.length)
            .put(iv)
            .put(encryptedSecretKey)
            .put(cryptedBytes)
            .array();
}

@Override
public byte[] decrypt(byte[] bytes) throws GeneralSecurityException {
    ByteBuffer buffer = ByteBuffer.wrap(bytes);
    byte[] iv = new byte[IV_SIZE];
    buffer.get(iv);

    //skip aes key bytes
    byte[] irrelevant = new byte[AES_KEY_SIZE * 8];
    buffer.get(irrelevant);

    byte[] encryptedMessage = new byte[bytes.length - IV_SIZE - irrelevant.length];
    buffer.get(encryptedMessage);

    Cipher cipher = Cipher.getInstance(AES_MODE);
    GCMParameterSpec parameterSpec = new GCMParameterSpec(AES_TAG_LEN, iv);
    cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), parameterSpec);

    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    try {
        for (int i = 0; i < encryptedMessage.length; i += CIPHER_CHUCK_SIZE) {
            int len = Math.min(CIPHER_CHUCK_SIZE, encryptedMessage.length - i);
            cipher.update(encryptedMessage, i, len);
        }
        bout.write(cipher.doFinal());
    } catch (IOException e) {
        e.printStackTrace();
        throw new GeneralSecurityException(e);
    }

    byte[] decryptedMessage = bout.toByteArray();
    return decryptedMessage;
}

Because of this fix I strongly believe that it must be a framework bug. if somebody with advanced knowledge of encryption could enlighten me, I would be very glad.

like image 322
Rafael T Avatar asked Sep 12 '18 17:09

Rafael T


1 Answers

I haven't found the root issue. All I can say is, that it seems to be NOT a bug!

I have simplified the de- and encryption as Following:

package com.dermalog.votercheck.crypto;

import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;


public class SimpleCrypto {

private final SecretKey mSecretKey;

public SimpleCrypto(SecretKey secretKey){
    mSecretKey = secretKey;
}

public byte[] encrypt(byte[] data) throws GeneralSecurityException {
    Cipher aes = Cipher.getInstance("AES/GCM/NoPadding");
    aes.init(Cipher.ENCRYPT_MODE, mSecretKey);
    GCMParameterSpec parameterSpec = aes.getParameters().getParameterSpec(GCMParameterSpec.class);
    byte[] encrypted = aes.doFinal(data);
    if (encrypted.length != aes.getOutputSize(data.length)){
        throw new GeneralSecurityException("Encrypted Output Size does not match cipher.getOutputSize");
    }
    return ByteBuffer.allocate(12 + encrypted.length).put(parameterSpec.getIV()).put(encrypted).array();
}

public byte[] decrypt(byte[] encryptedData) throws GeneralSecurityException{

    ByteBuffer buffer = ByteBuffer.wrap(encryptedData);
    byte[] iv = new byte[12];
    buffer.get(iv);
    GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv);

    Cipher aes = Cipher.getInstance("AES/GCM/NoPadding");
    aes.init(Cipher.DECRYPT_MODE, mSecretKey, parameterSpec);

    byte[] encryptedMessage = new byte[encryptedData.length - 12];
    buffer.get(encryptedMessage);
    return aes.doFinal(encryptedMessage);
}
}

and tested it (successfully) like this:

@Test
public void testSimpleCrypto() throws GeneralSecurityException {
    SecureRandom secureRandom = new SecureRandom();
    Random random = new Random();
    byte[] keyBytes = new byte[32];
    secureRandom.nextBytes(keyBytes);

    SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
    SimpleCrypto crypto = new SimpleCrypto(secretKey);

    String test = "SecretData";

    byte[] encrypted = crypto.encrypt(test.getBytes(StandardCharsets.UTF_8));
    byte[] decrypted = crypto.decrypt(encrypted);
    Assert.assertEquals(new String(decrypted, StandardCharsets.UTF_8), test);

    byte[] randomData = new byte[81920];
    random.nextBytes(randomData);

    encrypted = crypto.encrypt(randomData);
    decrypted = crypto.decrypt(encrypted);
    Assert.assertArrayEquals(randomData, decrypted);

    randomData = new byte[131073];
    random.nextBytes(randomData);

    encrypted = crypto.encrypt(randomData);
    decrypted = crypto.decrypt(encrypted);
    Assert.assertArrayEquals(randomData, decrypted);

    for (int i = 1024 * 20; i < 1024 * 300; i+= 1024) {
        randomData = new byte[i];
        random.nextBytes(randomData);

        encrypted = crypto.encrypt(randomData);
        decrypted = crypto.decrypt(encrypted);
        Assert.assertArrayEquals(randomData, decrypted);
    }
}

So The bug MUST be on my side of the implementation (although I have no clue why the fix I implemented is working then)

Just wanted to write a follow up.

BTW: If someone wants to grab the bounty just answer. Would be too bad if my reputation is lost for nothing ^^

like image 186
Rafael T Avatar answered Nov 02 '22 02:11

Rafael T