CryptoStream: Why CryptoStreamMode.Write to encrypt and CryptoStreamMode.Read to decrypt? - c#

Let e = 'password' and I am transforming it to 'as9kio0736' in a CryptoStream.
Let d = 'as9kio0736' and I am transforming it to 'password in a CryptoStream.
When I am transforming d back to 'password' why is it not considered writing in a CryptoStream?
using (MemoryStream msEncrypt = new MemoryStream()) {
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) {
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) {
swEncrypt.Write(plainText);
}
}
}
using (MemoryStream msDecrypt = new MemoryStream(cipherText)) {
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) {
using (StreamReader srDecrypt = new StreamReader(csDecrypt)) {
plaintext = srDecrypt.ReadToEnd();
}
}
}

CryptoStream is designed to perform transformation from a stream to another stream only and allows transformations chaining. For instance you can encrypt a data stream then Base 64 encode the encryption output.
Chose the mode depending on whether you want to write to the transformation input stream or read from the transformation output stream.
CryptoStream documentation is misleading.
The first CrytoStream constructor argument is described as:
"The stream on which to perform the cryptographic transformation"
This description is ok if constructor third argument value is CryptoStreamMode.Read.
But if third argument value is CryptoStreamMode.Write the first constructor argument description should be:
"The stream on which the result of cryptographic transformation is written to"
Also, documentation does not mention clearly that if you use CryptoStreamMode.Write, you MUST call FlushFinalBlock on your CryptoStream object after you finish writing.
To summarize this:
Writing to the transformation input stream:
CryptoStream constructor arguments:
argument 1: destination stream
argument 3: CryptoStreamMode.Write
CryptoStream object use:
Write data to the CryptoStream object
Call FlushFinalBlock on the CryptoStream object
Reading from the transformation output stream:
CryptoStream constructor arguments:
argument 1: source stream
argument 3: CryptoStreamMode.Read
CryptoStream object use:
Read data from the CryptoStream object until you reach the stream end

You can use CryptoStream in either direction for either operation; it's just where your data is and what you want to do with it.
If the data to process is already in a Stream (and you're okay with the stream getting drained and disposed), use CryptoStream in read mode and read the data out (including by using cryptoStream.CopyTo(someOtherStream)). If the data is in a byte[] and you want to write it to a Stream, use CryptoStream.Write.
In the .NET Core tests you can find examples both ways.
Using Read for both encrypt and decrypt: https://github.com/dotnet/corefx/blob/61fb32299a276f1aa4103c85dcec215dfddc252d/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs#L466-L480
Using Write for both encrypt and decrypt: https://github.com/dotnet/corefx/blob/61fb32299a276f1aa4103c85dcec215dfddc252d/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs#L609-L631
Heck, those are even in the same files. It's all just a matter of preference.

CryptoStream gives the flexibility to read/write for both Encrypt and Decrypt. For the case where the direction is fixed it can be changed to move in either direction regardless of the implementation.
Here's a way to do it:
https://github.com/djpai/StreamConduit

Related

.NET Core AES CryptoStream cipher always 16 bytes

I'm trying to encrypt/decrypt a string with AES, using streams. I'm using the following code for encryption:
var provider = Aes.Create();
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
using var encryptor = provider.CreateEncryptor();
using var memoryStream = new MemoryStream();
using var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
using var streamWriter = new StreamWriter(cryptoStream, Encoding.UTF8);
streamWriter.Write(plainText);
cryptoStream.FlushFinalBlock();
var cipher = memoryStream.ToArray();
This successfully produces a byte array, though no matter the plaintext length, the cipher is always 16 bytes. From my understanding, with a block size of 16, a plaintext string with a length of 16 or more should result in a cipher that is larger than 16 bytes. Also, even for plaintext that is less than 16 bytes, decryption always results in an empty string.
var provider = Aes.Create();
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
using var decryptor = _provider.CreateDecryptor(key, iv);
using var memoryStream = new MemoryStream(cipher);
using var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
using var streamReader = new StreamReader(cryptoStream, Encoding.UTF8);
var plainText = streamReader.ReadToEnd();
My code is based on this sample in the Microsoft docs, though I'm calling cryptoStream.FlushFinalBlock(), after writing to the stream, although this isn't working as desired.
Calling FlushFinalBlock is not really necessary if you correctly close the stream. It might be useful if you want to write the last block (including padding) without closing it.
However, using the generic streaming API, preferably with the using statement and closing the stream should write any bytes left in the buffer + any padding that could be required.
Of course you should include any stream that writes to the CryptoStream in that using statement, otherwise they may have leftover data. Of course the receiving stream should only be closed after the data has been retrieved, for instance in case a MemoryStream is used.

Read and Write to an XML

I have 2 methods as shown below which read and write to my XML file. What is the easiest method to encrypt the XML file and allow read/write of it from my code ?
Read XML File
XmlSerializer SerializerObj = new XmlSerializer(typeof(List<ItemsUnderControlObject>));
// Create a new file stream for reading the XML file
FileStream ReadFileStream = new FileStream(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location.ToString()) + #"\itemlist.xml", FileMode.Open, FileAccess.Read, FileShare.Read);
// Load the object saved above by using the Deserialize function
MyGlobals.ListOfItemsToControl = (List<ItemsUnderControlObject>)SerializerObj.Deserialize(ReadFileStream);
// Cleanup
ReadFileStream.Close();
Write XML File
// Create a new XmlSerializer instance with the type of the test class
XmlSerializer SerializerObj = new XmlSerializer(typeof(List<ItemsUnderControlObject>));
// Create a new file stream to write the serialized object to a file
TextWriter WriteFileStream = new StreamWriter(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location.ToString()) + #"\itemlist.xml");
SerializerObj.Serialize(WriteFileStream, MyGlobals.ListOfItemsToControl);
WriteFileStream.Close();
Look into System.Security.Cryptography namespace that provides a bunch of classes that let you encrypt/decrypt. Many will take a stream to en/decrypt, so just pass your WriteFileStream/ReadFileStream instances to a crypto class, and that'll do it.
The example below is based in part on example in MSDN on set up of AES crypto provider (and can be adapted for other crypto algorithms). It requires some initialization code that would be dependent on your implementation - see example there.
using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for encryption.
using (Stream msEncrypt = new FileStream(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location.ToString()) + #"\itemlist.xml"))
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
// Create a new XmlSerializer instance with the type of the test class
XmlSerializer SerializerObj = new XmlSerializer(typeof(List<ItemsUnderControlObject>));
// Create a new file stream to write the serialized object to a file
SerializerObj.Serialize(swEncrypt, MyGlobals.ListOfItemsToControl);
}
}
}
}
Code is best effort - don't have means to build it at the moment. So may need a bit of TLC... But should give a pretty good idea of what's needed.

Appending IV to an

I am in the process of writing a small encoding class which takes a byte[] array, and cyphers it. As the IV can be made public, I wish to prepend it to the ciphertext to use for decryption.
Following the MSDN Documentation and some SO Post source (Cant find link), I started the stream with the raw IV;
//Start encryption process
using (var msEncrypt = new MemoryStream())
{
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
{
using (var swWriter = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swWriter.Write(encryptor.IV);
swWriter.Write(plainTextRaw);
}
}
Console.WriteLine("Encrypted Value: {0}", BitConverter.ToString(msEncrypt.ToArray()).Replace("-", String.Empty));
return msEncrypt.ToArray();
}
However, upon writing a simple unit test extracting the first 16 bytes, they do not seem to match.
I am certain the IV is being encrypted within the stream, so where is the best place to inject it?
Just write the IV tot the initial memory stream msEncrypt, not to the stream that is being encrypted swWriter.

Chained GZipStream/DeflateStream and CryptoStream (AES) breaks when reading

I want to compress and then encrypt my data, and for improved speed (by not having to write to byte arrays and back) decided to chain the streams used for compression and encryption together.
It works perfectly when I write (compress and encrypt) the data, but when I try to read the data (decompress and decrypt), the Read operation breaks - simply calling Read once reads exactly 0 bytes, because the first Read always returns 0. Looping as in the below code almost works, except that at a certain point, Read stops returning anything > 0 even though there's still data to be read.
Everything before those last few bytes are decompressed and decrypted perfectly.
The number of bytes left when that happens remains the same for the same plaintext; for example, it's always 9 bytes for a certain string, but always 1 byte for another.
The following is the relevant encryption and decryption code; any ideas as to what could be going wrong?
Encryption:
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
using (DeflateStream zip = new DeflateStream(csEncrypt, CompressionMode.Compress, true))
{
zip.Write(stringBytes, 0, stringBytes.Length);
csEncrypt.FlushFinalBlock();
Decryption:
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream())
{
// Writes the actual data (sans prepended headers) to the stream
msDecrypt.Write(stringBytes, prependLength, stringBytes.Length - prependLength);
// Reset position to prepare for read
msDecrypt.Position = 0;
// init buffer to read to
byte[] buffer = new byte[originalSize];
using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
using (DeflateStream zip = new DeflateStream(csDecrypt, CompressionMode.Decompress))
{
// Hangs with "offset" at a small, deterministic number away from originalSize (I've gotten 9 less and 1 less for different strings)
// Loop fixed as per advice
int offset = 0;
while (offset < originalSize)
{
int read = zip.Read(buffer, offset, originalSize - offset);
if (read > 0)
offset += read;
else if (read < 0)
Console.WriteLine(read); // Catch it if it happens.
}
// Hangs with "left" at a small, deterministic number (I've gotten 9 and 1 for different strings)
/*
for (int left = buffer.Length; left > 0; )
left -= zip.Read(buffer, 0, left);
*/
Solution (Modification to Encryption):
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (DeflateStream zip = new DeflateStream(csEncrypt, CompressionMode.Compress, true))
zip.Write(stringBytes, 0, stringBytes.Length);
//Flush after DeflateStream is disposed.
csEncrypt.FlushFinalBlock();
The problem lies in the following line:
csEncrypt.FlushFinalBlock();
If you remove that, the code will work.
The reason is that when you write to DeflateStream, not all data is written to the underlying stream. That happens only when you call Close() or Dispose() explicitly or implicitly by leaving the using block.
So in your code, this happens:
You Write() all of the data to the DeflateStream, which in turn writes most of the data to the underlying CryptoStream.
You call csEncrypt.FlushFinalBlock(), which closes the CryptoStream.
You leave the using block of the DeflateStream, which tries to write the rest of the data to the already closed CryptoStream.
You leave the using block of the CryptoStream, which would call FlushFinalBlock(), if it wasn't called already.
The correct sequence of events is:
You Write() all of the data to the DeflateStream, which in turn writes most of the data to to the underlying CryptoStream.
You leave the using block of the DeflateStream, which writes the rest of the data to the already closed CryptoStream.
You leave the using block of the CryptoStream, which calls FlushFinalBlock().
Although I would expect that writing to a closed stream would fail with an exception. I'm not sure why that doesn't happen.
I had similar issue recently while reading from remote stream.
For me, solution was to change line which read entire stream in single call into while loop:
zip.Write(stringBytes, 0, stringBytes.Length);
Remote streams not always can return requested amount of data, so try read stream while you read enough bytes.

'Stream does not support seeking' with CryptoStream object

I am trying to encrypt some data with the following code:
public static byte[] EncryptString(byte[] input, string password)
{
PasswordDeriveBytes pderiver = new PasswordDeriveBytes(password, null);
byte[] ivZeros = new byte[8];
byte[] pbeKey = pderiver.CryptDeriveKey("RC2", "MD5", 128, ivZeros);
RC2CryptoServiceProvider RC2 = new RC2CryptoServiceProvider();
byte[] IV = new byte[8];
ICryptoTransform encryptor = RC2.CreateEncryptor(pbeKey, IV);
MemoryStream msEncrypt = new MemoryStream();
CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);
csEncrypt.Write(input, 0, input.Length);
csEncrypt.FlushFinalBlock();
return msEncrypt.ToArray();
}
However, when it reaches initializing my CryptoStream object, it throws the following error:
"Stream does not support seeking."
To clarify, there is no error handling in the above code, so just running this will not "break", persay. But stepping through the code, the CryptoStream object will show this error in its properties once it's been initialized.
Why is this? And how can I avoid it?
So the code actually runs without an exception, but the problem is when you're looking at the properties in the debugger? If so, that's easy - some properties (Position, for example) rely on being able to seek within the stream. You can't do that with a CryptoStream - so the property evaluation fails.
You don't need to avoid this - it's perfectly fine.
Can you use one of the constructors on the MemoryStream where you pass 'true' to the writable parameter?
To avoid this problem, its much easier to use:
using (var reader = new StreamReader(csEncrypt))
{
return reader.ReadToEnd();
}

Categories

Resources