Majlena Majlena - 4 months ago 44
Java Question

Java Encrypt a file using aes256/ CBC/PKCS7Padding

I'm trying to encrypt a file using aes256- CBC-PKCS7Padding . I'm using bouncy castle library , but I get exception

java.lang.IllegalArgumentException: invalid parameter passed to AES init - org.bouncycastle.crypto.params.ParametersWithIV
at org.bouncycastle.crypto.engines.AESEngine.init(Unknown Source)
at org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher.init(Unknown Source)


Here the source code :

public class Crypto {

public static final int AES_Key_Size = 256;
public static int blockSize = 16;
private final BlockCipher AESCipher = new AESEngine();
private PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(AESCipher, new PKCS7Padding());
private byte[] IV;
private KeyParameter key;

public Crypto() throws NoSuchAlgorithmException {
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(AES_Key_Size);
SecretKey sk = kg.generateKey();
key = new KeyParameter(sk.getEncoded());


}
public void CryptoZip(File plikZip, File plikAES) throws IOException, DataLengthException, IllegalStateException, InvalidCipherTextException {

byte[] input = Files.readAllBytes(plikZip.toPath());
byte[] cryptOut = encrypt(input);
FileOutputStream fos = new FileOutputStream(plikAES);
fos.write(cryptOut);
fos.close();



}


private byte[] encrypt(byte[] input) throws DataLengthException, IllegalStateException, InvalidCipherTextException {



IV = new byte[blockSize];
SecureRandom random = new SecureRandom();
random.nextBytes(IV);

cipher.init(true, new ParametersWithIV(key, IV)); // problem here


byte[] output = new byte[cipher.getOutputSize(input.length)];
int bytesWrittenOut = cipher.processBytes(
input, 0, input.length, output, 0);

cipher.doFinal(output, bytesWrittenOut);

return output;

}
}


Any suggestion how to fix it and explanation what I'm doing wrong will be really helpful.

Answer

What you're missing is the indication of the mode. If that is missing then ECB mode is assumed, and ECB mode doesn't take an IV. So PaddedBufferedBlockCipher does do buffering, but for ECB mode. So the init mode simply passes the parameters to AESEngine, and AESEngine rejects the IV as it only accepts keys.

In your code, the following would be a direct fix for the problem:

private final CBCBlockCipher AESCipherCBC = new CBCBlockCipher(AESCipher);
private final PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(AESCipherCBC, new PKCS7Padding());

I'll include the following rewrite to show you a different way of writing this down. Note that I didn't touch upon handling the IV or the exceptions correctly. Obviously, for large files, you may want to stream the contents and/or to map your files.

// renamed as crypto is a horrible name
public class FileEncryptor {

    // lets use all uppercase constant names
    public static final int AES_KEY_SIZE = 256;

    // only field needed, the rest can be generated on the fly
    private final KeyParameter key;

    public FileEncryptor() throws NoSuchAlgorithmException {
        key = generateKey();
    }

    private static KeyParameter generateKey() {
        // removed KeyGenerator as that's dependent on JCA crypto-API 
        SecureRandom keyRNG = new SecureRandom();
        byte[] keyData = new byte[AES_KEY_SIZE / Byte.SIZE];
        keyRNG.nextBytes(keyData);
        return new KeyParameter(keyData);
    }

    // the code doesn't do anything with zip itself, so no need to include it in the method name
    public void encryptFile(File plaintextFile, File ciphertextFile) throws IOException, DataLengthException, IllegalStateException, InvalidCipherTextException {
        byte[] plaintext = Files.readAllBytes(plaintextFile.toPath());
        byte[] ciphertext = encrypt(plaintext);
        // try and be symmetric, use Files functionality for reading *and writing*
        Files.write(ciphertextFile.toPath(), ciphertext);
    }


    private byte[] encrypt(byte[] plaintext) throws DataLengthException, IllegalStateException, InvalidCipherTextException {
        // create cipher
        final BlockCipher aes = new AESFastEngine();
        CBCBlockCipher aesCBC = new CBCBlockCipher(aes);
        PaddedBufferedBlockCipher aesCBCPadded =
                new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());

        // create IV
        byte[] iv = new byte[aes.getBlockSize()];
        SecureRandom random = new SecureRandom();
        random.nextBytes(iv);

        // initialize cipher with IV
        ParametersWithIV paramsWithIV = new ParametersWithIV(key, iv);
        aesCBCPadded.init(true, paramsWithIV); // problem here

        // encrypt
        byte[] ciphertext = new byte[aesCBCPadded.getOutputSize(plaintext.length)];
        int bytesWrittenOut = aesCBCPadded.processBytes(
            plaintext, 0, plaintext.length, ciphertext, 0);
        aesCBCPadded.doFinal(ciphertext, bytesWrittenOut);

        // that's great, but where is your IV now? you need to include it in the returned ciphertext!
        return ciphertext;
    }
}
Comments