Search code examples
c#encryptionaes

Add IV to beginning of CryptoStream


I'm implementing local encryption within an existing file management program.

Much of the example code I can find, such as Microsoft's, demonstrates how to write directly to a file, but what I need to do is provide a stream that is consumed elsewhere in the program:

CryptoStream GetEncryptStream(string filename)
{
    var rjndl = new RijndaelManaged();
    rjndl.KeySize = 256;
    rjndl.BlockSize = 256;
    rjndl.Mode = CipherMode.CBC;
    rjndl.Padding = PaddingMode.PKCS7;

    // Open read stream of unencrypted source fileStream:
    var fileStream = new FileStream(filename, FileMode.Open); 

    /* Get key and iv */

    var transform = rjndl.CreateEncryptor(key, iv);

    // CryptoStream in *read* mode:
    var cryptoStream = new CryptoStream(fileStream, transform, CryptoStreamMode.Read); 

    /* What can I do here to insert the unencrypted IV at the start of the
       stream so that the first X bytes returned by cryptoStream.Read are
       the IV, before the bytes of the encrypted file are returned? */

    return cryptoStream; // Return CryptoStream to be consumed elsewhere
}

My issue is outlined in the comment on the last line but one: how can I add the IV to the start of the CryptoStream such that it will be the first X bytes returned when the CryptoStream is read, given that control of when to actually start reading the stream and writing to a file is outside the scope of my code?


Solution

  • Ok... now that your problem is clear, it is "quite" easy... Sadly .NET doesn't include a class to merge two Stream, but we can easily create it. The MergedStream is a read-only, forward-only multi-Stream merger.

    You use like:

    var mergedStream = new MergedStream(new Stream[] 
    {
        new MemoryStream(iv),
        cryptoStream,
    });
    

    Now... When someone tries to read from the MergedStream, first the MemoryStream containing the IV will be consumed, then the cryptoStream will be consumed.

    public class MergedStream : Stream
    {
        private Stream[] streams;
        private int position = 0;
        private int currentStream = 0;
    
        public MergedStream(Stream[] streams) => this.streams = streams;
        
        public override bool CanRead => true;
    
        public override bool CanSeek => false;
    
        public override bool CanWrite => false;
    
        public override long Length => streams.Sum(s => s.Length);
    
        public override long Position 
        { 
            get => position; 
            set => throw new NotSupportedException();
        }
    
        public override void Flush()
        {
        }
    
        public override int Read(byte[] buffer, int offset, int count)
        {
            if (streams == null)
            {
                throw new ObjectDisposedException(nameof(MergedStream));
            }
    
            if (currentStream >= streams.Length)
            {
                return 0;
            }
    
            int read;
    
            while (true)
            {
                read = streams[currentStream].Read(buffer, offset, count);
                position += read;
    
                if (read != 0)
                {
                    break;
                }
    
                currentStream++;
    
                if (currentStream == streams.Length)
                {
                    break;
                }
            }
    
            return read;
        }
    
        public override long Seek(long offset, SeekOrigin origin)
            => throw new NotSupportedException();
    
        public override void SetLength(long value)
            => throw new NotSupportedException();
    
        public override void Write(byte[] buffer, int offset, int count)
            => throw new NotSupportedException();
    
        protected override void Dispose(bool disposing)
        {
            try
            {
                if (disposing && streams != null)
                {
                    for (int i = 0; i < streams.Length; i++)
                    {
                        streams[i].Close();
                    }
                }
            }
            finally
            {
                streams = null;
            }
        }
    }