user1327159
user1327159

Reputation: 151

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.

Upvotes: 0

Views: 3140

Answers (3)

user1327159
user1327159

Reputation: 151

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.

Upvotes: 2

Maarten Bodewes
Maarten Bodewes

Reputation: 93988

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.

Upvotes: 1

Rob Napier
Rob Napier

Reputation: 299355

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.

Upvotes: 3

Related Questions