10
votes

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?

2
Your code would be helpful here. Most likely your buffer is a fixed size and you're not truncating it to the correct length, but it could be several things that are impossible to tell without code.Rob Napier

2 Answers

12
votes

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.

0
votes

i am using c# from bouncycastle. looks to me this might be a bug from bouncycastle, or at least bouncycastle c# implementation does not follow pkcs7 spec exactly.

my solution is to chop off the trailing bytes that are not included in the return length of DoFinal. still not very sure why there are padding of 0x00, which as said should not exist at all.

below is the code. i used AES/CBC/PKCS7PADDING for both encryption and decryption.

encryption --->

        ICipherParameters keyParams = ParameterUtilities.CreateKeyParameter("AES", keyByte);
        ICipherParameters aesIVKeyParam = new ParametersWithIV(keyParams, StringToByteArray(IV_STRING));
        byte[] iv = ((ParametersWithIV) aesIVKeyParam).GetIV();

        IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CBC/PKCS7PADDING");
        cipher.Init(true, aesIVKeyParam);

        byte[] cipherText = new byte[iv.Length + cipher.GetOutputSize(data.Length)];
        Array.Copy(iv, 0, cipherText, 0, iv.Length);
        int length = cipher.DoFinal(data, 0, data.Length, cipherText, iv.Length);

decryption --->

        ICipherParameters keyParams = ParameterUtilities.CreateKeyParameter("AES", keyByte);
        byte[] iv = new byte[IV_LENGTH];
        Array.Copy(data, 0, iv, 0, IV_LENGTH);
        ICipherParameters aesIVKeyParam = new ParametersWithIV(keyParams, iv);

        IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CBC/PKCS7PADDING");
        cipher.Init(false, aesIVKeyParam);

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