Lowering speed on my hybride encryption function for large files

Posted on

Problem

I have an assignment to encrypt files (+- 1 gb) and speed is an important factor. I am also kinda forced to the C# language since nobody in my group knows C++. The time now recorded for 100 mb is 12 seconds could somebody give me advice if it is possible to improve the speed my encryption function below.

private void buttonSearch_Click(object sender, RoutedEventArgs e)
{
    if (fileSelected && fileDestination)
    {
        String key = AESEncrypt.GenerateKey();
        progressBar.Visibility = Visibility.Visible;
        try
        {
            // AES Encrypt the file
            AESEncrypt.EncryptFile(textBoxSelectFile.Text, textBoxDestinationFile.Text, key,login.PrivateKey, progressBar);

            SecurityDB.Login loginB = (SecurityDB.Login)loginDataGrid.SelectedItem;
            byte[] Storekey = RSAEncrypt.EncryptKey(loginB.PublicKey, key);
            AESEncrypt.CreateKey(Storekey, this.textBoxDestinationFile.Text+"\");

            // Writing PublicKey in destination Folder
            using (StreamWriter writer = new StreamWriter(this.textBoxDestinationFile.Text + "\publicKey.txt"))
            {
                writer.WriteLine(loginB.PublicKey);
            }

        }
        catch (Exception ex)
        {
            System.Windows.MessageBox.Show(ex.Message);
        }
    }
    else
    {
        System.Windows.MessageBox.Show("Select Folders");
    }
}

public static async void EncryptFile(string inputFile, string outputFile, string sKey, String privateKey, ProgressBar progress)
{
    String fileName = inputFile;
    fileName = "\" + fileName.Split('\').Last();
    var progres = new Progress<int>(value => progress.Value = value);
    UnicodeEncoding UE = new UnicodeEncoding();

    await Task.Run(async () =>
        {
            byte[] key = UE.GetBytes(sKey);

            FileStream fsCrypt = new FileStream(outputFile + fileName, FileMode.Create);

            RijndaelManaged RMCrypto = new RijndaelManaged();

            CryptoStream cs = new CryptoStream(fsCrypt,
                        RMCrypto.CreateEncryptor(key, key),
                        CryptoStreamMode.Write);

            FileStream fsIn = new FileStream(inputFile, FileMode.Open);

            int data;
            byte[] buffer = new byte[0x1000];
            double i = 0;

            while ((data = await fsIn.ReadAsync(buffer, 0, buffer.Length)) > 0)
            {
                cs.Write(buffer, 0, buffer.Length);
                i += buffer.Length;
                number = (int)(i / fsIn.Length * 100);
                ((IProgress<int>)progres).Report(number);
            }
            fsIn.Close();
            cs.Close();
            fsCrypt.Close();

            byte[] hash = Hashing.HashFile(outputFile + fileName, privateKey);
            File.WriteAllBytes(outputFile + "\Hashing.txt", hash);
        });
}

Solution

Multithreaded Approach

Since you are using WinForms you can use BackgroundWorker. BackgroundWorker will automatically use threads from the ThreadPool, and importantly, they have a notification event for when the thread completes.

There are several ways this could be done, and also keep in mind that I only have moderate experience with multithreading in C#.

You will need to create several BackgroundWorkers. I would start with 4 and adjust from there. We can also simplify reassembling the blocks by writing them to the output file as they complete.

Here is a high level overview for what you should do:

  1. Put the workers in a list of available threads.
  2. In the main thread, check if there is a free worker in the list.
    1. If Yes, read the next block from the file, pass it and the block index to the worker and start it. Remove the worker from the list
    2. If No, then sleep.
  3. After the worker has completed, it will call the RunWorkerCompleted event, where it will return its result (which should be the encrypted block and the block index.
  4. The event handler should write the encrypted block to the corresponding index in the output file, and then add the worker back to the list.
  5. Repeat until all the blocks have been processed.

Some Notes

The RunWorkerCompleted event may run on the background thread (or you may get errors about it) and we don’t want that. First, any updates to the UI, like updating the progress after a block is written, have to happen on the main UI thread. Secondly, we don’t want multiple threads trying to write to the output file at the same time. There is a simple trick to get the RunWorkCompleted event to run on the main UI thread.

private void bgworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{ 
    //if we aren't on the main thread, recursively call on main thread
    if(this.InvokeRequired)
    Invoke( ( MethodInvoker ) delegate {
        bgworker_RunWorkerCompleted(sender, e);
        return;  // don't forget to return or this method will do it twice
    });

   //Write to file
   //Update Progress
   //Add worker back to list
} 

The BackgroundWorker.RunWorkerAsync() and RunWorkerCompletedEventArgs only accept/return a single object and you need to pass two (the block index and the block itself). You will need to either use a tuple/struct/class or something else to hold both objects when passing back and forth.

Finally, You may need special handling for the last block of the file. Some encryption algorithms (zero) pad a short block to the expected length (1K in this case). This means that your encrypted file may be slightly longer than your unencrypted file. If this is the case for your chosen algorithm, your allocated output file size may be slightly longer than the original input. Depending on the file contents, this may or may not be an problem, however you should be aware of it as a potential issue.


Update: What I mean by Block Index

Let’s say a file is 10K. The first block has index 0 and is 1K in length. The second block has index 1, the third index 2, etc. The file has 10 blocks with indexes 0-9.

Now lets say that I queue things up and block index 4 finishes first. In my output file I seek to position 1000*blockIndex, or 4000 and write the 1K bytes. Then let’s say that block 2 finishes. I seek to position 2000 and write the 1K. My output file now looks like [0|0|*|0|*|0|0|0|0|0] where ‘*’ denotes valid data and ‘0’ denotes empty/nothing written.

For performance purposes, I’d probably let the UI thread open the output file at the beginning and only close it after the last block is written. You know the file is done when all of your workers are sitting in the list. (Workers also have a property to check their status :IsBusy)

Links

Leave a Reply

Your email address will not be published. Required fields are marked *