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();
}
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);
}
}
}