user1134179
user1134179

Reputation: 539

Spring Security Crypto final block not properly padded

I'm trying to create a proof of concept of file encryption with spring security crypto. Per some of the other posts I've found I have downloaded the Unlimited JCE Policy and added the jars to my java_home/jre/lib/security folder.

The error occurs when calling bytesEncryptor.decrypt. The encrypt may be working as there is encrypted content in the file after execution but I'm not sure of a way to confirm that's not where the problem lies. Some other posts about this error that aren't using Spring are saying the key is not right but this can't be because I'm using the same bytesEncryptor object? (Given final block not properly padded)

Stack:

Exception in thread "main" java.lang.IllegalStateException: Unable to invoke Cipher due to bad padding
at org.springframework.security.crypto.encrypt.CipherUtils.doFinal(CipherUtils.java:142)
    at org.springframework.security.crypto.encrypt.AesBytesEncryptor.decrypt(AesBytesEncryptor.java:128)
    at com.test.encryption.MyTest.crypt(MyTest.java:45)
    at com.test.encryption.MyTest.decryptFile(MyTest.java:31)
    at com.test.encryption.MyTest.main(MyTest.java:21)
Caused by: javax.crypto.BadPaddingException: Given final block not properly padded
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676)
    at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:313)
    at javax.crypto.Cipher.doFinal(Cipher.java:2087)
    at org.springframework.security.crypto.encrypt.CipherUtils.doFinal(CipherUtils.java:135)
... 4 more

Code:

public static void main(String args[]) throws Exception {
    String salt = KeyGenerators.string().generateKey();
    BytesEncryptor bytesEncryptor = Encryptors.standard("password", salt);

    encryptFile(bytesEncryptor, "C:/test/testIn.txt", "C:/test/testOut.txt");
    decryptFile(bytesEncryptor, "C:/test/testOut.txt", "C:/test/testOutDecode.txt");
}


private static void encryptFile (BytesEncryptor bytesEncryptor, String in, String out) throws Exception {
    crypt(bytesEncryptor, in, out, true);
}

private static void decryptFile (BytesEncryptor bytesEncryptor, String in, String out) throws Exception {
    crypt(bytesEncryptor, in, out, false);
}

private static void crypt (BytesEncryptor bytesEncryptor, String in, String out, boolean encrypt) throws Exception {
    byte[] buffer = new byte[1024];
    int numRead;
    byte[] bytes = null;
    InputStream input = new FileInputStream(in);
    OutputStream output = new FileOutputStream(out);

    while ((numRead = input.read(buffer)) > 0) {
        if(encrypt) {
            bytes = bytesEncryptor.encrypt(buffer);
        } else {
            bytes = bytesEncryptor.decrypt(buffer);
        }

        if (bytes != null) {
            output.write(bytes, 0, numRead);
        }
    }

    input.close();
    output.close();
}

Upvotes: 2

Views: 10252

Answers (3)

koljaTM
koljaTM

Reputation: 10262

For what it's worth, I got this message, when I was using the wrong password/salt to decrypt (same length though)

Upvotes: 1

user1134179
user1134179

Reputation: 539

Turns out you need to use a CipherInputStream and CipherOutputStream for FileInputStreams and FileOutputStreams.

Below is more or less taken from this http://www.programcreek.com/java-api-examples/index.php?source_dir=cube-master/cube-common/src/main/java/ch/admin/vbs/cube/common/crypto/AESEncrypter.java

Updated code:

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;

public class MyTest {

    private static String algorithm = "AES/CBC/PKCS5Padding";

    public static void main(String args[]) throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(256);
        SecretKey secretKey = keyGen.generateKey();

        final int AES_KEYLENGTH = 256;
        byte[] iv = new byte[AES_KEYLENGTH / 16];
        SecureRandom prng = new SecureRandom();
        prng.nextBytes(iv);

        Cipher aesCipherForEncryption = Cipher.getInstance(algorithm);
        Cipher aesCipherForDecryption = Cipher.getInstance(algorithm);
        aesCipherForEncryption.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
        aesCipherForDecryption.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));

        encryptFile(aesCipherForEncryption, "C:/test/testIn.txt", "C:/test/testOut.txt");
        decryptFile(aesCipherForDecryption, "C:/test/testOut.txt", "C:/test/testOutDecode.txt");
    }


    private static void encryptFile (Cipher cipher, String in, String out) throws Exception {
        crypt(cipher, in, out, true);
    }

    private static void decryptFile (Cipher cipher, String in, String out) throws Exception {
        crypt(cipher, in, out, false);
    }

    private static void crypt (Cipher cipher, String in, String out, boolean encrypt) throws Exception {
        byte[] buffer = new byte[256];
        int numRead;
        InputStream input = new FileInputStream(in);
        OutputStream output = new FileOutputStream(out);

        if(encrypt) {
            output = new CipherOutputStream(output, cipher);
        } else {
            input = new CipherInputStream(input, cipher);
        }

        while ((numRead = input.read(buffer)) >= 0) {
            output.write(buffer, 0, numRead);
        }

        input.close();
        output.close();
    }
}

Note - I did end up going away from using the Spring Security Crypto implementation.

Upvotes: 0

Qwerky
Qwerky

Reputation: 18445

I suspect the problem comes from they way you read your file in chunks of 1024 bytes. AES is a block cipher so operates on blocks of data of a certain size. When it enciphers it will pad output to make sure it fits into chunks of the appropriate size. When you give it data to decipher it expects the data to be similarly padded if necessary.

Try reading the whole file into a byte array, then encrypting and decrypting that. Alternatively you could try and use a chunk size matching the block size you are using for AES (it will be 128, 192 or 256 bits).

Upvotes: 0

Related Questions