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?
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;
}
}
}