Ошибка Android AES/GCM/NoPadding на определенной длине входных байтов?

я написал класс, который используется для шифрования произвольных данных.

это работает, как это: Потому что AndroidKeyStore сгенерированный ключ AES "потерян" после удаления приложения, у нас есть пара открытого/закрытого ключа, где открытый ключ включен в приложение. Для целей fail save он используется для шифрования ключа AES, который дополняется перед каждым зашифрованным сообщением вдоль IV. Таким образом, мы можем восстановить ключ AES с помощью нашего закрытого ключа

в блоке тест я обнаружил, что он работает, для большинства входов. Странно, что на определенной длине массива байтов он терпит неудачу (я нашел т. е. 81920, 131073.)

Итак, вот код AESCrypto:

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;
    }
}

функция модульного теста выглядит следующим образом:

 @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));

    }

}

какие журналы:

    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.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.run(ParentRunner.java:290)
        at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
        at org.junit.runners.ParentRunner.access0(ParentRunner.java:58)
        at org.junit.runners.ParentRunner.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.run(ParentRunner.java:290)
        at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
        at org.junit.runners.ParentRunner.access0(ParentRunner.java:58)
        at org.junit.runners.ParentRunner.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)

теперь большой вопрос: Есть ли ошибка в моей реализации или это ошибка фреймворка, о которой следует сообщить, чтобы предотвратить невозможность де-и шифрования данных определенная длина байта?

BTW: Я исправил проблему, используя буферизованный de и шифрование, как это (метод exchange в 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;
}

из-за этого исправить я твердо верю, что это должна быть ошибка фреймворка. если бы кто-то с передовыми знаниями шифрования мог просветить меня, я был бы очень рад.

1 ответов


Я не нашел корень проблемы. Все что я могу сказать, что это, кажется, не ошибка!

я упростил де-и шифрование следующим образом:

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);
}
}

и протестировал его (успешно) следующим образом:

@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);
    }
}

таким образом, ошибка должна быть на моей стороне реализации (хотя я понятия не имею, почему исправление, которое я реализовал, работает)

просто хотел написать продолжение.

BTW: если кто-то хочет захватить награду просто ответ. Было бы слишком плохо, если бы моя репутация была потеряна зря ^^