I find myself repeating myself with this code
using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read)
{
using (var aes = AesCryptoServiceProvider() { Key = ... }
{
// Read the IV at the beginning of the filestream
using (var cryptoStream = new CryptoStream(fileStream, aes.CreateDecryptor(), CryptoStreamMode.Read)
{
// Actual code only using cryptoStream
}
}
}
and
using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write)
{
using (var aes = AesCryptoServiceProvider() { Key = ... }
{
// Write the IV at the beginning of the filestream
using (var cryptoStream = new CryptoStream(fileStream, aes.CreateDecryptor(), CryptoStreamMode.Write)
{
// Actual code only using cryptoStream
}
}
}
And I ask myself, if it is possible to replace this with something like this
using (var cryptoStream = new MyDecryptionStream(path))
{
// Actual code
}
The actual code can be very different. It can be an image that has to be saved or an xml serialization.
I tried to implement my own Stream class, that casts all methods to a private property CryptoStream. But this didn't work out. It always broke at the counterpart, where I tried to read the IV at the beginning.
Here's an extremely rough example of what you're trying to do. There are a lot of places where improvement could be made, but it is a working sample that you can hopefully build on.
First, we create a class that implements IDisposable
. This allows us to use this class in using
statements. This class will instantiate the three other objects we need, and handle disposing them, all by itself.
class MyCryptoStream : IDisposable
{
private FileStream fileStream = null;
private AesCryptoServiceProvider aes = null;
public CryptoStream cryptoStream = null;
public enum Mode
{
Write,
Read
}
public MyCryptoStream(string filepath, Mode mode, byte[] key, byte[] iv = null)
{
if(mode == Mode.Write)
{
fileStream = new FileStream(filepath, FileMode.Open, FileAccess.Write);
fileStream.Write(iv, 0, 16);
aes = new AesCryptoServiceProvider() { Key = key, IV = iv };
cryptoStream = new CryptoStream(fileStream, aes.CreateEncryptor(), CryptoStreamMode.Write);
}
else
{
iv = new byte[16];
fileStream = new FileStream(filepath, FileMode.Open, FileAccess.Read);
fileStream.Read(iv, 0, 16);
aes = new AesCryptoServiceProvider() { Key = key, IV = iv };
cryptoStream = new CryptoStream(fileStream, aes.CreateDecryptor(), CryptoStreamMode.Read);
}
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
if (cryptoStream != null)
{
cryptoStream.Dispose();
}
if (aes != null)
{
aes.Dispose();
}
if (fileStream != null)
{
fileStream.Dispose();
}
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
disposedValue = true;
}
}
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
// ~UsingReduction() {
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
// Dispose(false);
// }
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion
}
Now, we can use this class like so:
string path = "..\\..\\test.txt";
byte[] key = null;
byte[] iv = null;
using (AesCryptoServiceProvider myAes = new AesCryptoServiceProvider())
{
key = myAes.Key;
iv = myAes.IV;
}
using (MyCryptoStream ur = new MyCryptoStream(path, MyCryptoStream.Mode.Write, key, iv))
{
using (StreamWriter sw = new StreamWriter(ur.cryptoStream))
{
sw.Write("Test string");
}
}
string text = string.Empty;
using (MyCryptoStream ur = new MyCryptoStream(path, MyCryptoStream.Mode.Read, key))
{
using (StreamReader sr = new StreamReader(ur.cryptoStream))
{
text = sr.ReadToEnd();
}
}
If you run this example, you can see that it writes "Test string"
to a file using the cryptostream
, and then reads the same text back from that file. Checking the value of text
, we can see that it is still "Test string"
, indicating that the procedure was successful.