Search code examples
c#-4.0cryptographyaesbouncycastle

Rijndael Decryption with BouncyCastle Producing a corrupted beginning and end of data but the rest of the data decrypts perfectly


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.


Solution

  • 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.