I am working on a project where I need to implement encryption for sending quite large amounts of data through a tcp connection. I wish to write it in C#. The project is also a part of my learning, by implementing such encryption I'll get experience.
I've made some research and tested some things out and I've written how I think I have to go about implementing it below.
Basically, I'd like to ask if someone could please check what I've written below and warn me about any mistakes, security holes or incorrect statements I made. Pointing those out and explaining what I should do to rectify the problems is greatly appreciated.
- We have host A and B connected via tcp.
- Host A creates an instance of
System.Security.Cryptography.RSA
viaRSA.Create(2048)
. By doing that, the RSA instance created random public and private keys. The private key can be used to decrypt data that was encrypted by the public key. The public key can be derived from the private key, but reverse is impossible. - A calls
rsa.ExportParameters(includePrivateParameters: false);
to export the public key and uses some serializer likeXmlSerializer
to serialize theRSAParameters
object into bytes. A can then send the serialized object via TCP with no encryption to B. - B receives and deserializes the
RSAParameters
object and uses it for creating it's own instance of RSA:RSA.Create(deserializedRSApar);
. The RSA object of B now has the public key of A's RSA and thus can encrypt data so that noone else but A can decrypt it because A has the private key. - B creates an instance of
System.Security.Cryptography.Aes
in CBC mode with 256 bit key and 16 byte IV. - B encrypts the AES key using the RSA object with the imported public key and sends it back to A.
- A receives the encrypted data and decrypts it using it's private key and creates it's own instance of
System.Security.Cryptography.Aes
and sets the key it decrypted. The RSA objects can now be disposed because both hosts have the same AES key. - Further communication will be encrypted with AES in separate messages, the hosts need to use a different unpredictable IV for each of the messages, IV will be prepended in plain bytes before the encrypted data. I will also be putting the length of the given message after IV and before the encrypted data.. I've written it to write the length of the data before encryption because I've heard that AES does not change data length when encrypting.
A part of the method used for sending should look something like this:
aes.GenerateIV();
using(var writer = new BinaryWriter(networkStream))
using(var encryptor = aes.CreateEncryptor())
using(var cryptoWriter = new CryptoStream(networkStream, encryptor, CryptoStreamMode.Write))
{
writer.Write(aes.IV);
writer.Write((int)bytesToSend.Length);
cryptoWriter.Write(bytesToSend, 0, bytesToSend.Length);
}
And a part of the method for receiving should look something like this:
using(var reader = new BinaryReader(networkStream))
using(var decryptor = aes.CreateDecryptor(aes.Key, reader.ReadBytes(aes.IV.Length)))
using(var cryptoReader = new CryptoStream(networkStream, decryptor, CryptoStreamMode.Read))
{
int toRead = reader.ReadInt32();
if (toRead > bufferArray.Length)
throw new Exception("Throw an exception or handle in another way, unimportant for now");
cryptoReader.Read(bufferArray, 0, toRead);
}
I know I should be creating those BinaryReaders and Writers out of scope of the method, I wrote the usings here (as well as that needless (int) cast) to show my intent better than "There is a BinaryReader in the class this snippet of a method is in"
Another question I'd like to ask is: Does it matter how long the messages will be? Is there a limit to how big the messages should be? I don't intend on sending messages bigger than 32kB.