AES PKCS7 padding

user908645 picture user908645 · Feb 18, 2015 · Viewed 38.2k times · Source

I just start learning Bouncy Castle for AES encryption/decryption. I am using AES/CBC/PKCS7PADDING with 256-bit key.

BC can encrypt and decrypt text successfully, however after decryption I notice that there are always a few padding of null (0x00), which therefore fails my hash comparison. For example, suppose original input string is “1234567890”, the decrypted byte array is always:

{0x49,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x48,0x00,0x00,0x00,0x00,0x00,0x00}

Why the padding is not 0x06,0x06,0x06,0x06,0x06,0x06? And is there any way to deterministically tell the padding length (could be 0) after encryption so that I can get exactly the same string before encryption?

Answer

Ebbe M. Pedersen picture Ebbe M. Pedersen · Feb 19, 2015

When you specify PKCS7, BC will add the padding to the data before encrypting, and remove it again when decrypting. PKCS7 with AES would always add at least 1 byte of padding, and will add enough data to make the input a multiple of the AES block size. When decrypting the padding is verified to be correct, and in the case of PKCS7 also serve as an indicator of how much of the last block of decrypted data is padding, and how much is real data.

If you try decrypting the encrypted and padded data without specifying PKCS7 in the decrypt step, the padding would still be in the decrypted data.

Edit:

To illustrate my point .. here is some Java code that encrypts "1234567890" with AES/CBC/PKCS7, and then decrypts it again both with and without the PKCS7 padding:

public class BCTest {
    public static void doTest() throws Exception {
        Security.addProvider(new BouncyCastleProvider());

        byte[] clearData = "1234567890".getBytes();
        SecretKey secretKey = new SecretKeySpec("0123456789ABCDEF".getBytes(), "AES");
        AlgorithmParameterSpec IVspec = new IvParameterSpec("0123456789ABCDEF".getBytes());

        // encrypt with PKCS7 padding
        Cipher encrypterWithPad = Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC");
        encrypterWithPad.init(Cipher.ENCRYPT_MODE, secretKey, IVspec);
        byte[] encryptedData = encrypterWithPad.doFinal(clearData);
        System.out.println("Encryped data (" + encryptedData.length + " bytes): \t" + toHexString(encryptedData));

        // decrypt with PKCS7 pad
        Cipher decrypterWithPad = Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC");
        decrypterWithPad.init(Cipher.DECRYPT_MODE, secretKey, IVspec);
        byte[] buffer1 = new byte[encryptedData.length]; 
        int decryptLen1 = decrypterWithPad.doFinal(encryptedData, 0, encryptedData.length, buffer1); 
        System.out.println("Decrypted with Pad (" + decryptLen1 + " bytes):  \t" + toHexString(buffer1));

        // decrypt without PKCS7 pad
        Cipher decrypterWithoutPad = Cipher.getInstance("AES/CBC/NOPADDING", "BC");
        decrypterWithoutPad.init(Cipher.DECRYPT_MODE, secretKey, IVspec);
        byte[] buffer2 = new byte[encryptedData.length]; 
        int decryptLen2 = decrypterWithoutPad.doFinal(encryptedData, 0, encryptedData.length, buffer2); 
        System.out.println("Decrypted without Pad (" + decryptLen2 + " bytes):\t" + toHexString(buffer2));
    }

    private static String toHexString(byte[] bytes) {
        return javax.xml.bind.DatatypeConverter.printHexBinary(bytes);
    }

    public static void main(String[] args) throws Exception {
        BCTest.doTest(); 
    }
}

Output:

Encryped data (16 bytes):           602CAE14358D0AC5C96E2D46D17E58E3
Decrypted with Pad (10 bytes):      31323334353637383930000000000000
Decrypted without Pad (16 bytes):   31323334353637383930060606060606

When decrypting with the padding option, the output have been striped of the padding - and the cipher indicates 10 bytes of decrypted data - the rest of the buffer is 0 filled. Decrypting without the padding option, results in the padding now being part of the decrypted data.

Edit2:

Now seeing the original code, confirms my hunch. The methode GetOutputSize don't return the output size of the decrypted string, but only the maximum needed space in an output buffer. The methode have the following documentation in the BC code:

/**
* return the size of the output buffer required for an update plus a
* doFinal with an input of len bytes.
*
* @param len the length of the input.
* @return the space required to accommodate a call to update and doFinal
* with len bytes of input.
*/

DoFinal returns the actual length of the decrypted data put in the buffer.

So in

byte[] plainTextBuffer = new byte[cipher.GetOutputSize(data.Length - IV_LENGTH)];
int length = cipher.DoFinal(data, iv.Length, data.Length - iv.Length, plainTextBuffer, 0);

The plainTextBuffer would be slightly larger than the actual decrypted data - the actual length of data would be in length.