0

I am attempting to encrypt and decrypt data using the bouncycastle library, upon decryption of the data the data the first block of information is corrupt as if it failed to be successfully decrypted and the last block was completely lost. I am new to the bouncycastle library, and I have been searching all over internet trying to find a sensible implementation of the AES encryption in CBC mode using PKCS7Padding but I haven't been able to come across much documentation. Any help provided would be greatly appreciated, also I should note that I am a student not a professional developer. Thanks.

`public class RijndaelCBC
{
    private int _keySize;
    private byte[] _passphrase;

    public byte[] _iv { get; private set; }

    private IBlockCipher blockCipher;
    private PaddedBufferedBlockCipher aesCipher;
    private ParametersWithIV _param;

    public RijndaelCBC(int KeySize, string Passphrase)
    {
        if (Passphrase.Length < KeySize / 8)
            Passphrase = Passphrase.PadRight(KeySize / 8, '0');
        if (Passphrase.Length > KeySize)
            Passphrase = Passphrase.Substring(0, KeySize / 8);

        _passphrase = System.Convert.FromBase64String(Passphrase);
        Array.Resize(ref _passphrase, KeySize / 8);
        _keySize = KeySize;

        Random rnd = new Random();
        _iv = new byte[_keySize / 8];
        for (int t = 0; t < _keySize / 8; t++)
            rnd.Next();
        rnd.NextBytes(_iv);

        if (_keySize != 128 && _keySize != 192 && _keySize != 256)
            throw new Exception(string.Format("Invalid key size of {0} provided, cannot continue with the process.", _keySize));
    }

    public RijndaelCBC(int KeySize, string Passphrase, byte[] iv)
    {
        if (Passphrase.Length < KeySize / 8)
            Passphrase = Passphrase.PadRight(KeySize / 8, '0');
        if (Passphrase.Length > KeySize)
            Passphrase = Passphrase.Substring(0, KeySize / 8);


        _passphrase = System.Convert.FromBase64String(Passphrase);
        Array.Resize(ref _passphrase, KeySize / 8);
        _keySize = KeySize;
        _iv = iv;
        if (_keySize != 128 && _keySize != 192 && _keySize != 256)
            throw new Exception(string.Format("Invalid key size of {0} provided, cannot continue with the process.", _keySize));
    }

    public byte[] Encrypt(byte[] data)
    {
        try
        {
            blockCipher = new CbcBlockCipher(new RijndaelEngine(_keySize));
            _param = new ParametersWithIV(new KeyParameter(_passphrase), _iv);
            blockCipher.Init(true, _param);

            aesCipher = new PaddedBufferedBlockCipher(blockCipher, new Pkcs7Padding());
            byte[] cipherTextBlock = null; 
            int blockSize = aesCipher.GetBlockSize();
            List<byte> output = new List<byte>();
            int outputLen = 0;
            int chunkPosition = 0;
            for (chunkPosition = 0; chunkPosition < data.Length; chunkPosition += blockSize)
            {
                byte[] dataToProcess = new byte[blockSize];
                int chunkSize = (data.Length - chunkPosition) < blockSize ? (data.Length - chunkPosition) : blockSize;
                Buffer.BlockCopy(data, chunkPosition, dataToProcess, 0, chunkSize);

                cipherTextBlock = new byte[blockSize];
                outputLen = aesCipher.ProcessBytes(dataToProcess, 0, chunkSize, cipherTextBlock, 0);
                output.AddRange(cipherTextBlock);
            }
            try
            {
                if(chunkPosition < data.Length &&
                    chunkPosition + blockSize > data.Length)
                {
                    byte[] dataToProcess = new byte[blockSize];
                    int chunkSize = (chunkPosition + blockSize) - data.Length;
                    Buffer.BlockCopy(data, chunkPosition, dataToProcess, 0, chunkSize);
                    aesCipher.DoFinal(cipherTextBlock, 0);
                    output.AddRange(cipherTextBlock);
                }
            }
            catch (CryptoException ex)
            {}
            return output.ToArray();
        }
        catch (System.Exception ex)
        { }
        return null;
    }

    public byte[] Decrypt(byte[] data)
    {
        try
        {
            blockCipher = new CbcBlockCipher(new RijndaelEngine(_keySize));
            _param = new ParametersWithIV(new KeyParameter(_passphrase), _iv);
            blockCipher.Init(false, _param);

            aesCipher = new PaddedBufferedBlockCipher(blockCipher, new Pkcs7Padding());
            byte[] cipherTextBlock = null;   
            int blockSize = aesCipher.GetBlockSize();
            List<byte> output = new List<byte>();
            int outputLen = 0;
            int chunkPosition = 0;
            for (chunkPosition = 0; chunkPosition < data.Length; chunkPosition += blockSize)
            {
                byte[] dataToProcess = new byte[blockSize];
                int chunkSize = (data.Length - chunkPosition) < blockSize ? (data.Length - chunkPosition) : blockSize;
                Buffer.BlockCopy(data, chunkPosition, dataToProcess, 0, chunkSize);
                cipherTextBlock = new byte[blockSize];

                outputLen = aesCipher.ProcessBytes(dataToProcess, 0, chunkSize, cipherTextBlock, 0);
                output.AddRange(cipherTextBlock);
            }
            try
            {
                if (chunkPosition < data.Length &&
                    chunkPosition + blockSize > data.Length)
                {
                    byte[] dataToProcess = new byte[blockSize];
                    int chunkSize = (chunkPosition + blockSize) - data.Length;
                    Buffer.BlockCopy(data, chunkPosition, dataToProcess, 0, chunkSize);
                    aesCipher.DoFinal(cipherTextBlock, 0);
                    output.AddRange(cipherTextBlock);
                }
            }
            catch (CryptoException ex)
            { }
            return output.ToArray();
        }
        catch (System.Exception ex)
        { }
        return null;
    }
}`

Example of output: beginning of data should be:

Production Version Releases

But Ends up as: [¨dZJÊ)uól)ȱýº—ÑÚ~VE’·ðúœ×ñð ersion Releases

and end of data should be: for such products, Intel assumes no liability whatsoever, and Intel disclaims any express or implied warranty, relating to sale and/or use of Intel products, including liability or warranties relating to fitness for a particular purpose, merchantability or infringement of any patent, copyright or other intellectual property right. Intel products are not intended for use in medical, lifesaving, or life-sustaining applications.

but ends up as: for such products, Intel assumes no liability whatsoever, and Intel disclaims any express or implied warranty, relating to sale and/or use of Intel products, including liability or warran

I was trying to encrypt and decrypt an example however at the end data is lost and at the beginning the data isn't decrypted properly, but the rest of the file 52KB was perfect.

user1327159
  • 151
  • 1
  • 8
  • 1
    Could you convert your code example into a [SSCCE](http://sscce.org) that we can copy/paste and observe the issue ourselves? – Duncan Jones Oct 05 '12 at 07:45
  • If you expect your code to work when you are done with it, you shouldn't catch Exception nor CryptoException. If you expect your code to work in some cases and the rest to be a buggy mess, do leave it in. – Henrik Oct 05 '12 at 08:40
  • Something tells me that a local varialble keySizeBytes would be a good idea. – Maarten Bodewes Oct 05 '12 at 08:54

3 Answers3

3

There are several problems in this code, but the one that's causing your decryption problems are your use of the IV. You generate a random IV during encryption (good), but you then throw it away. You then generate a different random IV during decryption, which is incorrect. You need to use the same IV for decryption as encryption. You typically pass the IV along with the ciphertext (usually prepended to the ciphertext is easiest).

Your key generation is also incorrect. As best I can tell, you're expecting a "passphrase" in Base-64 encoding. You then chop it off at the key length, or pad it out with 0s. This is highly insecure. You're basically turning AES-256 into AES-50 or so. It looks encrypted, but it actually has a tiny keyspace and can be brute forced.

The correct way to convert a human-typed password into an AES key is with an algorithm called PBKDF2. I am not particularly familiar with bouncycastle, and don't know what provider they're using for PBKDF2. See PBKDF2 with bouncycastle in Java for some more bouncycastle-specific details.

Community
  • 1
  • 1
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • thanks for your comments, but the same iv that I used for encryption is the same iv used during decryption as I had gotten the iv using the _iv property, but I do believe that the second part with the passphrase may be what is causing the issue as I didn't convert it to an AES key. Again thanks. – user1327159 Oct 04 '12 at 19:39
  • Nope. If the key is incorrect, then nothing of your plain text will decrypt (to the correct value, with symmetric encryption a block will always decrypt). – Maarten Bodewes Oct 04 '12 at 19:57
  • I generated the key using the PbeParametersGenerator and its still producing the same problem, it seems to be an issue of memory as when I called aesCipher.ProcessBytes on a temporary buffer outside of the loop then started the decryption process as shown above I will get the start of the data decrypted but however some data from the random block of memory still gets copied over to my output byte[] array as if the BlockCipher still had some data in its buffer which gets copied to my output, so i have tried both solutions in this answer and both still have failed – user1327159 Oct 04 '12 at 22:47
  • It's unclear from your code how you're instantiating your RijndaelCBC. I'll trust that you intend to use the encryption IV in both cases. That said, your symptom in the first block is exactly the symptom you expect when you use the wrong IV. See the highest-rated answer here for a good explanation of how CBC works and why you see this effect: http://crypto.stackexchange.com/questions/2865/why-does-cbc-decryption-with-a-wrong-iv-still-give-readable-results. My comment about PBE has nothing to do with the garbage block. Failure to use PBE is undermining the security, not the decryption. – Rob Napier Oct 04 '12 at 23:41
  • You may want to put logging in and make sure that your IVs really are the same. – Rob Napier Oct 04 '12 at 23:42
2

What I ended up doing to get the code to work was to change the padded block cipher to a buffered block cipher, not sure if they are any negative implications of doing this but I included the code just in case anyone needs it as it is difficult to find examples for bouncycastle in C#

public class RijndaelCBC
{
    private int _keyBitSize;
    public byte[] _iv { get; private set; }

    private BufferedBlockCipher cipher;
    private KeyParameter key;
    private ParametersWithIV IVkey;

    public RijndaelCBC(int KeySize, byte[] salt, string Passphrase)
    {
    _keyBitSize = KeySize;

        Random rnd = new Random();

        _iv = new byte[_keyBitSize / 8];
        for (int t = 0; t < _keyBitSize / 8; t++)
            rnd.Next();
        rnd.NextBytes(_iv);

        PbeParametersGenerator generator = new Pkcs5S2ParametersGenerator();
        generator.Init(PbeParametersGenerator.Pkcs5PasswordToUtf8Bytes((Passphrase).ToCharArray()), salt, 1000);
        key = (KeyParameter)generator.GenerateDerivedParameters("AES", _keyBitSize);
    }

    public RijndaelCBC(int KeySize, byte[] salt, string Passphrase, byte[] iv)
    {
        _iv = iv;

        PbeParametersGenerator generator = new Pkcs5S2ParametersGenerator();
        generator.Init(PbeParametersGenerator.Pkcs5PasswordToUtf8Bytes((Passphrase).ToCharArray()), salt, 1000);
        key = (KeyParameter)generator.GenerateDerivedParameters("AES", _keyBitSize);
    }


    public byte[] Encrypt(byte[] data)
    {
        IBlockCipher theCipher = null;
        theCipher = new RijndaelEngine(_keyBitSize);
        cipher = new PaddedBufferedBlockCipher(new CbcBlockCipher(theCipher));
        IVkey = new ParametersWithIV(key, _iv);
        cipher.Init(true, IVkey);

        int size = cipher.GetOutputSize(data.Length);
        byte[] result = new byte[size];
        int olen = cipher.ProcessBytes(data, 0, data.Length, result, 0);
        olen += cipher.DoFinal(result, olen);

        if (olen < size)
        {
            byte[] tmp = new byte[olen];
            Array.Copy(result, 0, tmp, 0, olen);
            result = tmp;
        }

        return result;
    }

    public byte[] Decrypt(byte[] data)
    {
        IBlockCipher theCipher = null;
        theCipher = new RijndaelEngine(_keyBitSize);
        cipher = new PaddedBufferedBlockCipher(new CbcBlockCipher(theCipher));
        IVkey = new ParametersWithIV(key, _iv);
        cipher.Init(false, IVkey);

        int size = cipher.GetOutputSize(data.Length);
        byte[] result = new byte[size];
        int olen = cipher.ProcessBytes(data, 0, data.Length, result, 0);
        olen += cipher.DoFinal(result, olen);

        if (olen < size)
        {
            byte[] tmp = new byte[olen];
            Array.Copy(result, 0, tmp, 0, olen);
            result = tmp;
        }

        return result;
    }
}

As to the question of initialization of the class asked above, the first constructor generates the IV using random data, which can be used for encryption, after generation of the said IV you can obtain the IV by accessing the _iv property. Or you can pass the IV during encryption and/or decryption using the second class constructor.

user1327159
  • 151
  • 1
  • 8
1

Well, the reason you don't get the first block correctly must be the IV value or a buffer handling issue. That last one might be more logical than you expect, since the end is missing because you use the DoFinal the wrong way. You supply it the input buffer instead of the output buffer.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263