Reputation: 494
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?
Upvotes: 3
Views: 1428
Reputation: 111890
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;
}
}
}
Upvotes: 5
Reputation: 494
Thanks for those who've taken the time to answer. In the end I realized I have to have knowledge of the IV length in the buffering code, there's no way around it, so elected to keep it simple:
Encryption method (Pseudo-code):
/* Get key and IV */
outFileStream.Write(IV); // Write IV to output stream
var transform = rijndaelManaged.CreateEncryptor(key, iv);
// CryptoStream in read mode:
var cryptoStream = new CryptoStream(inFileStream, transform, CryptoStreamMode.Read);
do
{
cryptoStream.Read(chunk, 0, blockSize); // Get and encrypt chunk
outFileStream.Write(chunk); // Write chunk
}
while (chunk.Length > 0)
/* Cleanup */
Decryption method (Pseudo-code):
/* Get key */
var iv = inFileStream.Read(ivLength); // Get IV from input stream
var transform = rijndaelManaged.CreateDecryptor(key, iv);
// CryptoStream in write mode:
var cryptoStream = new CryptoStream(outFileStream, transform, CryptoStreamMode.Write);
do
{
inFileStream.Read(chunk, 0, blockSize); // Get chunk
cryptoStream.Write(chunk); // Decrypt and write chunk
}
while (chunk.Length > 0)
/* Cleanup */
Upvotes: -1
Reputation: 94018
It is not a good design to use a CryptoStream
to communicate between two local parties. You should use a generic InputStream
or pipe (for inter-process communication) instead. Then you can combine a MemoryStream
for the IV and a CryptoStream
and return the combination. See the answer of xanatos on how to do this (you may still need to fill in the Seek
functionality if that's required).
A CryptoStream
will only ever be able to handle ciphertext. As you need to change the code at the receiver anyway if you'd want to decrypt you might as well refactor to InputStream
.
If you're required to keep the current design then there is a hack available. First "decrypt" the IV using ECB mode without padding. As a single block cipher call always succeeds the result will be a block of data that - when encrypted using the CipherStream
- turns into the IV again.
Steps:
CipherStream
;CipherStream
using the key and an all zero, 16 byte IV; CipherStream
;You will need to create an InputStream
that first receives the decrypted IV (as MemoryStream
) and then the plaintext (as FileStream
) for this to be feasible. Again, also see the answer of xanatos on how to do this. Or see for instance this combiner and this HugeStream
on good ol' StackOverflow. Then use the combined stream as source for the CipherInputStream
.
But needless to say hacks like these should be well documented and removed at the earliest convenience.
Notes:
OutputStream
would generally make more sense for encryption, there may be other things wrong with the design.Upvotes: 2