Search code examples
c#encryptionstreamcryptostream

Add cleartext bytes to the beginning of a CryptoStream?


I have an interface defined like so:

public interface IEncryptionService
{
    Stream Encrypt(Stream cleartext);
    Stream Decrypt(Stream encrypted);
}

I am implementing this interface with an AesCryptoServiceProvider, but there's clearly a problem here. The IV (Initialization Vector) is not returned on the interface... so encrypting something would work fine, as long as I have no desire to decrypt it ever again. The Decrypt() method has no chance at all of working.

What I want to do is include the IV in cleartext at the beginning of the stream, then add the CryptoStream to it, so it is essentially encrypted data with a "header" that I could strip off and use for decrypting the stream.

So... how would I do that? I can create the CryptoStream easy enough, but it looks like this would encrypt the IV, which kinda defeats the purpose. I could load the CryptoStream into memory, prepend the IV, and then stream it out as a MemoryStream, but this would be really inefficient, and would die on large streams.

What is a good, secure, scalable practice for this?


Solution

  • Here is what I had in mind. See how you write the IV to the MemoryStream and then follow it with the crypto? Then when you want to decrypt, pull the IV off first in the same way.

    Sorry, been a long time. This one is working. It should scale well if you don't cast ms toArray(); at the end. For example write to FileStream as you go and you should not need much memory at all. This is just to demo prepending the IV.

        private byte[] encrypt(byte[] originalPlaintextBytes)
        {
            using (SymmetricAlgorithm algorithm = AesCryptoServiceProvider.Create())
            {
                algorithm.GenerateKey();
                algorithm.GenerateIV();
                byte[] iv = algorithm.IV;
                byte[] key = algorithm.Key;
                using (ICryptoTransform encryptor = algorithm.CreateEncryptor(key, iv))
                {
                    using (MemoryStream ms = new MemoryStream())
                    using (CryptoStream cs = new CryptoStream(ms, encryptor,CryptoStreamMode.Write))
                    {
                        BinaryWriter bw = new BinaryWriter(ms);
                        bw.Write(iv);
                        cs.Write(originalPlaintextBytes, 0, originalPlaintextBytes.Length);
                        return ms.ToArray();
                    }
                }
            }
        }
    

    OK rather than edit the above code, here is a whole program that randomly creates a plaintext file of 1 megabyte. Then it encrypts it into ciphertext. Note that this program does not ever need 1 megabyte of memory in which to operate. It is completely scalable. Again, as before, this program is to demonstrate the concept, and you would do better with a readBuffer larger than 1 byte. But I did not want to create that and obscure the core answer. I hope this helps. I think it is exactly the kind of approach you need.

    using System;
    using System.IO;
    using System.Security.Cryptography;
    using System.Windows.Forms;
    
    namespace SO_AES
    {
        public partial class Form1 : Form
        {
            Random ran = new Random();
            public Form1()
            {
                InitializeComponent();
                using (var file = File.Open("Plaintext.txt", FileMode.OpenOrCreate))
                {
                    byte[] junkBytes = new byte[1000];
                    for (int i = 0; i < 1000; i++)
                    {
                        for (int j = 0; j < 1000; j++)
                        {
                            junkBytes[j] = (byte)ran.Next(0, 255);
                        }
                        file.Write(junkBytes, 0, junkBytes.Length);
                    }
                }
    
                using (var plainTextFile = File.Open("Plaintext.txt", FileMode.Open))
                {
                    using (var cryptoTextFile = File.Open("Crypto.txt", FileMode.OpenOrCreate))
                    {
                        encrypt(plainTextFile, cryptoTextFile);
                    }
                }
            }
    
            void encrypt(Stream inStream, Stream outStream)
            {
                using (SymmetricAlgorithm algorithm = AesCryptoServiceProvider.Create())
                {
                    algorithm.GenerateKey();
                    algorithm.GenerateIV();
                    byte[] iv = algorithm.IV;
                    byte[] key = algorithm.Key;
                    using (ICryptoTransform encryptor = algorithm.CreateEncryptor(key, iv))
                    {
                        using (CryptoStream cs = new CryptoStream(outStream, encryptor, CryptoStreamMode.Write))
                        {
                            BinaryWriter bw = new BinaryWriter(outStream);
                            bw.Write(iv);
                            byte[] readBuffer = new byte[1];
                            BinaryReader br = new BinaryReader(inStream);
                            while (br.Read(readBuffer, 0, readBuffer.Length) != 0)
                            {
                                cs.Write(readBuffer, 0, 1);
                            }
                        }
                    }
                }
                inStream.Close();
                outStream.Close();
            }
        }
    }