2

I am trying to encrypt/decrypt using AES-GCM and JDK 1.8 CipherOutputStream, But getting BadPaddingException during decryption. I am using same IV and secret key during encryption and decryption, but not sure what is going wrong. Please see the code below:

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

Above code results in following error during decryption at line 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)
4

1 回答 1

1
  • Encryption doesn't work properly: In encrypt, CipherOutputStream#close must be called before FileOutputStream#close. This is because CipherOutputStream#close calls Cipher#doFinal that generates the tag and appends it to the ciphertext. This portion can only be written to the FileOutputStream-instance if FileOutputStream#close has not yet been called. By the way, CipherOutputStream#flush doesn't need to be called.

  • There is also a problem with decryption: In decrypt, outputStream.write(data) must be replaced by outputStream.write(data, 0, read). Otherwise usually too much data will be written to the FileOutputStream-instance.

  • The classes javax.crypto.CipherInputStream and javax.crypto.CipherOutputStream may perform the authentication false positive and are therefore not suitable for GCM-mode, e.g. from the documentation (Java 12) for CipherInputStream:

    This class may catch BadPaddingException and other exceptions thrown by failed integrity checks during decryption. These exceptions are not re-thrown, so the client may not be informed that integrity checks failed. Because of this behavior, this class may not be suitable for use with decryption in an authenticated mode of operation (e.g. GCM). Applications that require authenticated encryption can use the Cipher API directly as an alternative to using this class.

    Therefore, either the Cipher API should be used directly, as recommended in the documentation, or the BouncyCastle-implementations org.bouncycastle.crypto.io.CipherInputStream and org.bouncycastle.crypto.io.CipherOutputStream, e.g. for encryption:

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

    and analog for decryption.

    Note that even if authentication fails, decryption is performed, so the developer must ensure that the result is discarded and not used in this case.

于 2019-09-24T11:57:34.953 回答