BadPaddingException: проверка Mac в GCM не удалась - PullRequest
2 голосов
/ 23 сентября 2019

Я пытаюсь зашифровать / расшифровать, используя AES-GCM и JDK 1.8 CipherOutputStream, но получаю BadPaddingException во время дешифрования.Я использую тот же IV и секретный ключ во время шифрования и дешифрования, но не уверен, что происходит неправильно.Пожалуйста, смотрите код ниже:

 static String AES_GCM_MODE = "AES/GCM/NoPadding";

    SecretKey secretKey;

    public SymmetricFileEncryption(){

        Security.insertProviderAt( new BouncyCastleProvider(), 1);
        setSecretKey();
    }

    public static void main(String[] args) throws Exception {

        File inputFile = new File("test.txt");
        File outputFile = new File("test-crypt.txt");
        File out = new File("test-decrypt.txt");

        SymmetricFileEncryption sym = new SymmetricFileEncryption();
        sym.encrypt(inputFile, outputFile);
        sym.decrypt(outputFile, out);
    }

    public Cipher getEncryptionCipher() throws InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException {

        Cipher cipher = Cipher.getInstance(AES_GCM_MODE, "BC");
        GCMParameterSpec parameterSpec = new GCMParameterSpec(128, getInitializationVector());
        cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(), new IvParameterSpec(getInitializationVector()) );
        return cipher;
    }

    private Cipher getDecryptionCipher(File inputFile) throws InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, IOException, NoSuchProviderException {
        //initialize cipher
        Cipher cipher = Cipher.getInstance(AES_GCM_MODE, "BC");
        GCMParameterSpec parameterSpec = new GCMParameterSpec(128, getInitializationVector());
        cipher.init(Cipher.DECRYPT_MODE, getSecretKey(),new IvParameterSpec(getInitializationVector()) );
        return cipher;
    }

    public void encrypt(File inputFile, File outputFile) throws Exception {
        Cipher cipher = getEncryptionCipher();
        FileOutputStream fos = null;
        CipherOutputStream cos = null;
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(inputFile);
            fos = new FileOutputStream(outputFile);
            cos = new CipherOutputStream(fos, cipher);
            byte[] data = new byte[16];
            int read = fis.read(data);
            while (read != -1) {
                cos.write(data, 0, read);
                read = fis.read(data);
            }
            cos.flush();
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            fos.close();
            cos.close();
            fis.close();
        }
        String iv = new String(cipher.getIV());
    }

    public void decrypt(File inputFile, File outputFile) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, IOException, NoSuchProviderException {

        Cipher cipher = getDecryptionCipher(inputFile);
        FileInputStream inputStream = null;
        FileOutputStream outputStream = null;
        CipherInputStream cipherInputStream = null;

        try{
            inputStream = new FileInputStream(inputFile);
            cipherInputStream = new CipherInputStream(inputStream, cipher);
            outputStream = new FileOutputStream(outputFile);
            byte[] data = new byte[16];
            int read = cipherInputStream.read(data);
            while(read != -1){
                outputStream.write(data);
                read = cipherInputStream.read(data);
            }
            outputStream.flush();
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            cipherInputStream.close();
            inputStream.close();
            outputStream.close();
        }
    }

    public void setSecretKey(){
        SecureRandom secureRandom = new SecureRandom();
        byte[] key = new byte[16];
        secureRandom.nextBytes(key);
        secretKey =  new SecretKeySpec(key, "AES");
    }

    public SecretKey getSecretKey(){
        return secretKey;
    }

public byte[] getInitializationVector(){

        String ivstr = "1234567890ab"; //12 bytes
        byte[] iv =  ivstr.getBytes();//new byte[12];
        return iv;
 }

Приведенный выше код приводит к следующей ошибке при расшифровке в строке int read = cipherInputStream.read (data);

javax.crypto.BadPaddingException: mac check in GCM failed
    at javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:128)
    at javax.crypto.CipherInputStream.read(CipherInputStream.java:246)
    at javax.crypto.CipherInputStream.read(CipherInputStream.java:222)
    at com.rocketsoftware.abr.encryption.SymmetricFileEncryption.decrypt(SymmetricFileEncryption.java:107)

1 Ответ

1 голос
/ 24 сентября 2019
  • Шифрование не работает должным образом: в encrypt, CipherOutputStream#close необходимо назвать перед FileOutputStream#close.Это связано с тем, что CipherOutputStream#close вызывает Cipher#doFinal, который генерирует тег и добавляет его в зашифрованный текст.Эта часть может быть записана в экземпляр FileOutputStream, только если FileOutputStream#close еще не был вызван.Кстати, CipherOutputStream#flush не нужно вызывать.

  • Существует также проблема с расшифровкой: в decrypt, outputStream.write(data) необходимо заменить на outputStream.write(data, 0, read),В противном случае обычно слишком много данных будет записано в FileOutputStream -экземпляр.

  • Классы javax.crypto.CipherInputStream и javax.crypto.CipherOutputStream могутвыполните аутентификацию ложное срабатывание и, следовательно, не подходят для режима GCM, например, из документации (Java 12) для CipherInputStream:

    ЭтоКласс может перехватывать BadPaddingException и другие исключения, вызванные неудачными проверками целостности во время расшифровки.Эти исключения не перебрасываются, поэтому клиент не может быть проинформирован о том, что проверки целостности не пройдены.Из-за этого поведения этот класс может не подходить для использования с расшифровкой в ​​аутентифицированном режиме работы (например, GCM).Приложения, которым требуется аутентифицированное шифрование, могут использовать API шифра напрямую в качестве альтернативы использованию этого класса.

    Поэтому либо API шифра следует использовать напрямую, как рекомендовано в документации, либо реализации BouncyCastle org.bouncycastle.crypto.io.CipherInputStream и org.bouncycastle.crypto.io.CipherOutputStreamНапример, для шифрования:

    import org.bouncycastle.crypto.io.CipherInputStream;
    import org.bouncycastle.crypto.io.CipherOutputStream;
    import org.bouncycastle.crypto.engines.AESEngine;
    import org.bouncycastle.crypto.modes.AEADBlockCipher;
    import org.bouncycastle.crypto.modes.GCMBlockCipher;
    import org.bouncycastle.crypto.params.AEADParameters;
    import org.bouncycastle.crypto.params.KeyParameter;
    ...
    public void encrypt(File inputFile, File outputFile) throws Exception {
    
        AEADBlockCipher cipher = getEncryptionCipher();
        // Following code as before (but with fixes described above)
        ...
    }
    
    public AEADBlockCipher getEncryptionCipher() throws Exception {
    
        AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
        cipher.init(true, // encryption 
            new AEADParameters(
                new KeyParameter(getSecretKey().getEncoded()),  
                128, // tag length
                getInitializationVector(),                      
                "Optional Associated Data".getBytes()));                    
        return cipher;
    }
    ...
    

    и аналог для расшифровки.

    Обратите внимание, что даже если аутентификация не удалась, расшифровка выполняется, поэтому разработчик должен убедиться, что результат отбрасывается и не используется в этом случае.

...