5

I am new to encryption and am encrypting files using the following method:

private static void encryptFile(string filePath, byte[] password, byte[] salt)
{
    Rfc2898DeriveBytes rdb = new Rfc2898DeriveBytes(password, salt, 1000);
    AesManaged algorithm = new AesManaged();

    byte[] rgbKey = rdb.GetBytes(algorithm.KeySize / 8);
    byte[] rgbIV = rdb.GetBytes(algorithm.BlockSize / 8);
    GCHandle keyHandle = GCHandle.Alloc(rgbKey, GCHandleType.Pinned);
    GCHandle IVHandle = GCHandle.Alloc(rgbIV, GCHandleType.Pinned);

    ICryptoTransform cryptoAlgorithm = algorithm.CreateEncryptor(rgbKey, rgbIV);

    using (FileStream readStream = File.Open(filePath, FileMode.Open))
    {
        using (FileStream writeStream = new FileStream(filePath + ".enc", FileMode.Create, FileAccess.Write))
        {
            using (CryptoStream cryptoStream = new CryptoStream(writeStream, cryptoAlgorithm, CryptoStreamMode.Write))
            {
                while (readStream.Position < readStream.Length)
                {
                    byte[] buffer = new byte[4096];
                    int amountRead = readStream.Read(buffer, 0, buffer.Length);
                    cryptoStream.Write(buffer, 0, amountRead);
                }
                cryptoStream.Flush();
            }
        }
    }

    UtilityMethods.destroyBytes(rgbKey);
    UtilityMethods.destroyBytes(rgbIV);
    keyHandle.Free();
    IVHandle.Free();
}

What I want to do is multithread the process for faster encryption. Using a single thread, this has taken over 5 min to encrypt a ~3GB file. I'm looking to be able to do that encryption in under 1 min if possible (under 30 seconds would be fantastic, but I think I might be stretching).

I believe the answer is to create multiple streams (although I'm not sure), assigning each stream a chunk of the file to encrypt, but I'm not sure how to "break the file apart" to assign a chunk to each stream, or "put the file back together" after each part has gone through the stream that it was assigned to. Could someone point me in the right direction?

Thanks very much!

P.S. I've viewed this (Rijndael algorithm and CryptoStream: is it possible to encrypt / decrypt multithreaded?), but I don't understand the answer (ECB, CBC?). If the answer to my question lies there, could you possibly provide some sample code to get me going in the right direction?

Thanks again!

Community
  • 1
  • 1
Brian
  • 81
  • 3
  • Parallel/multithread encryption is possible with ECB only because otherwise the single block depend on each other. About ECB, CBC: see [Encryption operating modes: ECB vs CBC](https://adayinthelifeof.nl/2010/12/08/encryption-operating-modes-ecb-vs-cbc/) and [CipherMode Enumeration](https://msdn.microsoft.com/en-US/library/system.security.cryptography.ciphermode(v=vs.110).aspx). Notice also the remark about ECB: "Important: This mode is not recommended because it opens the door for multiple security exploits." – H.G. Sandhagen Jan 19 '17 at 05:54
  • This is likey a I/O problem not a CPU problem. Try the same code with the input and output on separate physical disks or with SSDs and you will see better performance. – Scott Chamberlain Jan 19 '17 at 17:01

2 Answers2

3

CBC (Cipher Block Chaining) is the default block mode for AesManaged.

You cannot encrypt different blocks in parallel with AES if you are using CBC because the side effect of encrypting block 1, for example, is to set a new IV for block 2. This is called "IV feedback." If you are running in parallel, this won't work. You will need to pick a different cipher block mode.

You could use ECB mode (which doesn't have IV feedback), but if you do, blocks that repeat themselves in plaintext will also repeat themselves in ciphertext, which opens you up to certain types of attacks.

Best would be to use CTR, where IV is based on a counter, but alas .NET does not seem to support it.

I would suggest you break the single file into several "virtual" files, each with its own cipherstream. Use CBC and populate the IV with bytes from the RNGCryptoServiceProvider. Seed the RNGCryptoServiceProvider with a value derived from the index of the virtual file within the physical file. That way you have an IV that varies from block to block and repeated plaintext won't show up in the ciphertext. Repeat the process when decrypting.

See also this question.

I would suggest you post your solution on security.stackexchange.com for review when you are ready.

Community
  • 1
  • 1
John Wu
  • 50,556
  • 8
  • 44
  • 80
  • I agree with your answer but I'm not sure I agree with your solution. Do you not think that implementing CTR mode yourself (which is relatively straight forward) would be a better approach than what you suggest? Interested in your opinion. – Luke Joshua Park Jan 19 '17 at 08:37
  • If performance is the problem we are solving, implementing math-intensive encryption in c# might not be helpful. My suggested solution *should* be OK, or at least no worse that using CBC over and over. But you are right, it is a dangerous thing to roll your own security. Probably the best answer is to purchase a third party CTR implementation and use that. – John Wu Jan 19 '17 at 19:58
  • How is CTR math intensive? Is uses no more xor operations than CBC mode does. Or are you talking about the speed of compiled C# itself? – Luke Joshua Park Jan 19 '17 at 20:05
  • Speed of c# itself. The .NET encryption libraries call unmanaged computational functions which can perform much faster than IL. Also, they can leverage dedicated crypto hardware if it is installed via WIn32 Crypto API. If you roll your own you can do neither, unless you are really really dedicated and don't mind writing C++. – John Wu Jan 19 '17 at 20:06
  • Interesting that there could be a significant difference. I might write an example I think. – Luke Joshua Park Jan 19 '17 at 20:08
0

You need to use Stream.Seek for your read stream. Have a look at the question at: StreamReader and seeking to help you avoid common pitfalls.

Keep in mind that you will have to remember the start & stop position of each stream precisely (particularly the encrypted stream length) otherwise you'll be unable to decrypt your file slices.

Good luck!

Community
  • 1
  • 1
Monza
  • 745
  • 4
  • 12