I have created a stream that takes a plaintext input stream, and it's read method will return cypertext based on the input stream;
I've been reading that it is recommended to use the Rfc2898DeriveBytes class to generate the key, but I'm not confident on the correct usage here.
None of the current Microsoft examples around the AES class, for example, seem to talk about the recommended use using password-based key derivation such as what Rfc2898DeriveBytes provides.
Do I still have a key or do I use Rfc2898DeriveBytes to generate the key? and if so, do I need to store something additional along with the encrypted data so I can decrypt? I don't have a good mental model for Rfc2898DeriveBytes would be integrated with what I've built.
public class AesEncryptionStream : Stream { private Stream _output; public override bool CanRead => _output.CanRead; public override bool CanSeek => _output.CanSeek; public override bool CanWrite => _output.CanWrite; public override long Length => _output.Length; public override long Position { get => _output.Position; set => _output.Position = value; } public byte[] InitializationVector { get; private set; } private bool _writeIVToStream; public AesEncryptionStream(byte[] key, Stream input, bool writeIVToStream) { _writeIVToStream = writeIVToStream; _output = SetupCryptoStream(key,input); } private Stream SetupCryptoStream(byte[] key, Stream input) { var algoritm = this.CreateEncryptionAlgorithm(key.Length * 8); this.InitializationVector = this.GenerateInitializationVector(algoritm); var transform = this.GetTransform(algoritm, key); return new CryptoStream(input, transform, CryptoStreamMode.Read); } protected virtual ICryptoTransform GetTransform(SymmetricAlgorithm algorithm, byte[] key) { return algorithm.CreateEncryptor(key, this.InitializationVector); } protected virtual byte[] GenerateInitializationVector(SymmetricAlgorithm algorithm) { algorithm.GenerateIV(); return algorithm.IV; } protected SymmetricAlgorithm CreateEncryptionAlgorithm(int keySizeBits) { var encryptor = Aes.Create(); encryptor.KeySize = keySizeBits; return encryptor; } public override void Flush() { _output.Flush(); } private int _position = 0; /// <summary> /// Reads from the plaintext (decrypted) input stream, and outputs in cyphertext (ecrypted). /// If the stream was set to include the initialization vector in the stream, it will be returned first /// </summary> /// <param name="buffer"></param> /// <param name="offset"></param> /// <param name="count"></param> /// <returns></returns> public override int Read(byte[] buffer, int offset, int count) { int read = 0; if (_writeIVToStream) { //if we need to return the IV, or part of it if (_position < this.InitializationVector.Length) { var ivToCopy = Math.Min(this.InitializationVector.Length - _position, count); Buffer.BlockCopy(this.InitializationVector, _position, buffer, offset, ivToCopy); _position += ivToCopy; read = ivToCopy; } } //if we haven't read enough if (read < count) { read += _output.Read(buffer, offset + read, count - read); } return read; } public override long Seek(long offset, SeekOrigin origin) { return _output.Seek(offset, origin); } public override void SetLength(long value) { _output.SetLength(value); } public override void Write(byte[] buffer, int offset, int count) { _output.Write(buffer, offset, count); } }
Rfc2898DeriveBytesimplements PBKDF2, i.e. the password based key derivation function 2 (it's badly named if just because the RFC also contains PBKDF1, so yeah...). You need it if you have a password. You'd need a 128 bit salt, a high iteration count, possibly a version number for your protocol and probably other measures to make sure that the password is not easy to crack. I'd always use an output stream for encryption and an input stream for decryption. I mean, you're not going to do anything special such as reading / writing objects with the ciphertext, right?