代码之家  ›  专栏  ›  技术社区  ›  Rafael T

Android AES/GCM/nop在特定长度的输入字节上添加Bug?

  •  8
  • Rafael T  · 技术社区  · 7 年前

    我编写了一个类,用于对任意数据进行反加密。

    它是这样工作的: 因为一个 AndroidKeyStore 生成的AES密钥在卸载应用程序后“丢失”,我们有一个公钥/私钥对,其中公钥包含在应用程序中。 出于保存失败的目的,它被用来加密AES密钥,该密钥沿着IV填充在每个加密消息的前面。这样,我们就可以用私钥恢复AES密钥

    在单元测试中,我发现它对大多数输入都有效。奇怪的是,在某些字节数组长度上它失败了(我发现81920131073)

    这是AES密码:

    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$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)
    

    现在的大问题是:

    顺便说一句:

    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 回复  |  直到 7 年前
        1
  •  1
  •   Rafael T    7 年前

    我还没有找到根本问题。我只能说,这似乎不是虫子!

    我已将数据消除和加密简化如下:

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

    所以这个bug一定是在我这边实现的(尽管我不知道为什么我实现的修复程序在那时可以工作)

    只是想写一篇后续文章。

    顺便说一句:如果有人想得到赏金,就回答。如果我的名誉白白丧失,那就太糟糕了^^