Search code examples
encryptionaesbouncycastleblock-cipher

Bouncy Castle AES Encryption - providing input in blocks


I'm using Bouncy Castle library to encrypt some data in my Windows Store App. My EncryptHelper class:

public static class EncryptHelper
{
    private const string KEY = "chiaveAES";
    private const int SIZE = 16;
    private enum CipherMode
    { 
        Encrypt,
        Decrypt
    }

    private static PaddedBufferedBlockCipher InitCipher(CipherMode mode)
    {
        PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CbcBlockCipher(new AesLightEngine()), new ZeroBytePadding());

        var key = new byte[32];
        var keyArray = KEY.ToCharArray();
        Buffer.BlockCopy(keyArray, 0, key, 0, Math.Min(keyArray.Length, key.Length));
        cipher.Init(mode == CipherMode.Encrypt, new KeyParameter(key));
        return cipher;
    }

    public static async Task Encrypt(Stream sourceStream, Stream destinationStream, bool autoSeekStart = true, bool autoSeekEnd = true)
    {
        //await Process(InitCipher(CipherMode.Encrypt), sourceStream, destinationStream, autoSeekStart, autoSeekEnd);
        await ProcessBlocks(InitCipher(CipherMode.Encrypt), sourceStream, destinationStream, autoSeekStart, autoSeekEnd);
    }


    public static async Task Decrypt(Stream sourceStream, Stream destinationStream, bool autoSeekStart = true, bool autoSeekEnd = true)
    {
        //await Process(InitCipher(CipherMode.Decrypt), sourceStream, destinationStream, autoSeekStart, autoSeekEnd);
        await ProcessBlocks(InitCipher(CipherMode.Decrypt), sourceStream, destinationStream, autoSeekStart, autoSeekEnd);
    }

    private static async Task Process(PaddedBufferedBlockCipher cipher, Stream sourceStream, Stream destinationStream, bool autoSeekStart, bool autoSeekEnd)
    {
        if (autoSeekStart)
        {
            sourceStream.ToBegin();
            destinationStream.ToBegin();
        }

        var size = Convert.ToInt16(sourceStream.Length);
        byte[] inBuffer = new byte[size];
        byte[] outBuffer = new byte[cipher.GetOutputSize(size)];
        int inCount = 0;
        int outCount = 0;

        try
        {
            inCount = await sourceStream.ReadAsync(inBuffer, 0, inBuffer.Length);

            outCount = cipher.ProcessBytes(inBuffer, 0, inCount, outBuffer, 0);
            outCount += cipher.DoFinal(outBuffer, outCount);

            await destinationStream.WriteAsync();

            await destinationStream.FlushAsync();
        }
        catch { }

        if (autoSeekEnd)
        {
            sourceStream.ToBegin();
            destinationStream.ToBegin();
        }
    }

    private static async Task ProcessBlocks(PaddedBufferedBlockCipher cipher, Stream sourceStream, Stream destinationStream, bool autoSeekStart, bool autoSeekEnd)
    {
        if (autoSeekStart)
        {
            sourceStream.ToBegin();
            destinationStream.ToBegin();
        }

        byte[] inBuffer = new byte[SIZE];
        byte[] outBuffer = new byte[cipher.GetOutputSize(SIZE)];
        int inCount = 0;
        int outCount = 0;

        try
        {
            while ((inCount = await sourceStream.ReadAsync(inBuffer, 0, inBuffer.Length)) > 0)
            {
                outCount += cipher.ProcessBytes(inBuffer, 0, inCount, outBuffer, 0);
                await destinationStream.WriteAsync(outBuffer, 0, outBuffer.Length);
            }

            outBuffer = ?
            outCount += cipher.DoFinal(outBuffer, outCount);

            await destinationStream.WriteAsync(outBuffer, 0, outCount);

            await destinationStream.FlushAsync();
        }
        catch { }

        if (autoSeekEnd)
        {
            sourceStream.ToBegin();
            destinationStream.ToBegin();
        }
    }
}

My Process() method works fine, but when on the instruction

inCount = await sourceStream.ReadAsync(inBuffer, 0, inBuffer.Length);

I'm afraid it may occurr an OutOfMemoryException if the stream has too much data. So, I was trying to build the ProcessBlocks() method, which should read from the stream progressively, one block per time, without overcharging the RAM. I have some doubts on how to behave with outBuffer: it should be overwritten in every cycle in which cipher.ProcessBytes() gets executed, but of which size should it be just before the cipher.DoFinal() invocation?

Thank you

UPDATE 30/07/2015

I modified the Main in the answer to handle a zip file and the outcoming zip file is no more a valid ZIP, could someone explain me why?

 public static void Main(string[] args)
    {
            var plainPath = @"C:\Users\Federico\Desktop\0abed72d-defc-4c9a-a8ae-3fec43f01224.zip";
            var decryptPath = @"C:\Users\Federico\Desktop\0abed72d-defc-4c9a-a8ae-3fec43f01224 - decrypted.zip";

            var plainStream = new FileStream(plainPath, FileMode.Open, FileAccess.Read);
            var cipherStream = new MemoryStream();
            EncryptHelper.Encrypt(plainStream, cipherStream);
            cipherStream.Seek(0, SeekOrigin.Begin);
            FileStream fs = new FileStream(decryptPath, FileMode.Create);
            EncryptHelper.Decrypt(cipherStream, fs);

            fs.Flush();
            fs.Close();
        }

Solution

  • cipher.DoFinal() will produce as many as 2 * Cipher.GetBlockSize() bytes. The actual number of bytes produced is returned by the method.

    Here is an example that is loosely based on your example.

    using System;
    using System.IO;
    
    using Org.BouncyCastle.Crypto.Paddings;
    using Org.BouncyCastle.Crypto.Parameters;
    using Org.BouncyCastle.Crypto.Modes;
    using Org.BouncyCastle.Crypto.Engines;
    using System.Text;
    
    namespace PaddedBufferedBlockCipherExample
    {
        public class EncryptHelper
        {
            private const string KEY = "chiaveAES";
            private const int BufferSize = 1024;
            private PaddedBufferedBlockCipher cipher;
    
            public enum CipherMode
            {
                Encrypt,
                Decrypt
            }
    
            public EncryptHelper (CipherMode mode)
            {
                cipher = new PaddedBufferedBlockCipher (new CbcBlockCipher (new AesLightEngine ()), new Pkcs7Padding ());
    
                var key = new byte[32];
                var keyArray = KEY.ToCharArray ();
                Buffer.BlockCopy (keyArray, 0, key, 0, Math.Min (keyArray.Length, key.Length));
                cipher.Init (mode == CipherMode.Encrypt, new KeyParameter (key));
            }
    
            public static void Encrypt (Stream sourceStream, Stream destinationStream)
            {
                var helper = new EncryptHelper (CipherMode.Encrypt);
                helper.ProcessBlocks (sourceStream, destinationStream);
            }
    
    
            public static void Decrypt (Stream sourceStream, Stream destinationStream)
            {
                var helper = new EncryptHelper (CipherMode.Decrypt);
                helper.ProcessBlocks (sourceStream, destinationStream);
            }
    
    
            private void ProcessBlocks (Stream sourceStream, Stream destinationStream)
            {
    
    
                // inBuffer is sized for efficient I/O
                var inBuffer = new byte[BufferSize];
    
                // outBuffer should be large enough to not require further resizing
                var outBuffer = new byte[cipher.GetBlockSize() +  cipher.GetOutputSize (inBuffer.Length)];
                int inCount = 0;
                int outCount = 0;
    
                // Process data using the cipher.ProcessBytes method, until we reach EOF
    
                while ((inCount = sourceStream.Read (inBuffer, 0, inBuffer.Length)) > 0) {
                    outCount = cipher.ProcessBytes (inBuffer, 0, inCount, outBuffer, 0);
                    destinationStream.Write (outBuffer, 0, outCount);
                }
    
                // Now "flush" the cipher instance by calling the DoFinal method. This
                // will finish the en/de-cryption by ciphering any buffered data and processing any
                // encryption padding.
    
                outCount = cipher.DoFinal (outBuffer, 0);
    
                destinationStream.Write (outBuffer, 0, outCount);
            }
    
            public static void Main (string[] args)
            {
                var plainPath = "/Users/robert/src/csharp_toys/toy1/Program.cs";
                var plainStream = new FileStream (plainPath, FileMode.Open, FileAccess.Read);
                var cipherStream = new MemoryStream ();
                EncryptHelper.Encrypt (plainStream, cipherStream);
                cipherStream.Seek (0, SeekOrigin.Begin);
                var decryptedStream = new MemoryStream ();
                EncryptHelper.Decrypt (cipherStream, decryptedStream);
                var decryptedString = Encoding.ASCII.GetString (decryptedStream.ToArray ());
                Console.Write (decryptedString);
            }
        }
    }