I doubt you will come back and accept an answers two years later, however I just did what you are asking about, and I think it is a fairly common problem so I am posting this for the benefit of others that may come across this question.
I incorporated GregS' information into my implementation. For your specific purpose, you would make the Initialize method your constructor, strip out the net & diffie-hellman code, and assign your pre-shared key to the Aes object (instead of the generated key).
Please note that I am using AES256 despite that it has been compromised to being of similar strength to AES128 (if your keys are related because of bad implementation, which mine are not to my knowledge). If you do not trust NIST to know if the NSA is messing with their specs, then do NOT use AES.
Also, this is a starting point! I am working through the commonly encountered problem of sending encrypted data over a NetworkStream in .NET!
In a hundred twelve lines or less, without further ado:
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
namespace FullDuplexCrypto
{
class CryptoNetworkStream : Stream
{
public CryptoNetworkStream(IPAddress address, int port)
{
Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect(address, port);
//socket.NoDelay = true;
Initialize(new NetworkStream(socket, true));
}
public CryptoNetworkStream(Socket socket)
{
Initialize(new NetworkStream(socket, true));
}
private void Initialize(Stream stream)
{
underlyer = stream;
using(ECDiffieHellmanCng dh = new ECDiffieHellmanCng())
{
dh.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
dh.HashAlgorithm = CngAlgorithm.Sha256;
byte[] buffer = dh.PublicKey.ToByteArray();
underlyer.Write(buffer, 0, buffer.Length);
underlyer.Read(buffer, 0, buffer.Length);
using(Aes aes = Aes.Create())
{
aes.KeySize = 256;
aes.Key = dh.DeriveKeyMaterial(CngKey.Import(buffer, CngKeyBlobFormat.EccPublicBlob));
aes.FeedbackSize = 8;
aes.Mode = CipherMode.CFB;
underlyer.Write(aes.IV, 0, aes.IV.Length);
encrypter = new CryptoStream(underlyer, aes.CreateEncryptor(), CryptoStreamMode.Write);
underlyer.Read(aes.IV, 0, aes.IV.Length);
decrypter = new CryptoStream(underlyer, aes.CreateDecryptor(), CryptoStreamMode.Read);
}
}
}
private Stream underlyer;
private Stream encrypter;
private Stream decrypter;
public override bool CanRead { get { return decrypter.CanRead; } }
public override bool CanWrite { get { return encrypter.CanWrite; } }
public override bool CanSeek { get { return underlyer.CanSeek; } }
public override long Length { get { return underlyer.Length; } }
public override long Position { get { return underlyer.Position; } set { underlyer.Position = value; } }
public override void Flush()
{
encrypter.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
return decrypter.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count)
{
encrypter.Write(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{
return underlyer.Seek(offset, origin);
}
public override void SetLength(long value)
{
underlyer.SetLength(value);
}
private bool isDisposed = false;
protected override void Dispose(bool isDisposing)
{
if(!isDisposed)
{
if(isDisposing)
{
// Release managed resources.
encrypter.Dispose();
decrypter.Dispose();
underlyer.Dispose();
}
// Release unmanaged resources.
isDisposed = true;
}
base.Dispose(isDisposing);
}
}
}