Search code examples
c#cryptographyencryption-asymmetric

Exchange symetric keys between server and client in a secure way


I have an algorithm that I believe to be secure. So the server creates a public and private key combination for every new connection. In this example I am just dealing with one thread but in reality I will have to construct several threads so that the server can be listening for multiple connections.

The client is Alice and it also creates a public and private key combination. here is how the client Alice creates a random symmetric key to give to the server bob securely.

server code (Bob) :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Security.Cryptography;
using System.IO;


namespace ServerListener
{
    class Program
    {
        static TcpListener server;

        static CngKey bobKey;          // server private key
        static byte[] alicePubKeyBlob; // client public key
        static byte[] bobPubKeyBlob;   // server public key

        static byte[] symetricKey;     // the symetric key that will later be used to transfer data more efficeintly

        static void Main(string[] args)
        {

            // create server private and public keys
            CreateKeys(); 

            // start listening for new connections
            IPAddress ipAddress = IPAddress.Parse("192.168.0.120");
            server = new TcpListener(ipAddress, 54540);
            server.Start();
            var client = server.AcceptTcpClient();

            // once a connection is established open the stream
            var stream = client.GetStream();

            // we need the client public key so we need to instantiate it.
            alicePubKeyBlob = new byte[bobPubKeyBlob.Length];

            // waint until the client send us his public key
            stream.Read(alicePubKeyBlob, 0, alicePubKeyBlob.Length);

            // alicePubKeyBlob should now be the client's public key

            // now let's send this servers public key to the client
            stream.Write(bobPubKeyBlob, 0, bobPubKeyBlob.Length);

            // encrytpedData will be the data that server will recive encrypted from the client with the server's public key
            byte[] encrytpedData = new byte[1024];
            // wait until client sends that data
            stream.Read(encrytpedData, 0, encrytpedData.Length);

            // decrypt the symetric key with the private key of the server
            symetricKey = BobReceivesData(encrytpedData);

            // server and client should know have the same symetric key in order to send data more efficently and securely
            Console.Read();

        }

        private static void CreateKeys()
        {
            //aliceKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256);
            bobKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256);
            //alicePubKeyBlob = aliceKey.Export(CngKeyBlobFormat.EccPublicBlob);
            bobPubKeyBlob = bobKey.Export(CngKeyBlobFormat.EccPublicBlob);
        }

        private static byte[] BobReceivesData(byte[] encryptedData)
        {
            Console.WriteLine("Bob receives encrypted data");
            byte[] rawData = null;

            var aes = new AesCryptoServiceProvider();

            int nBytes = aes.BlockSize >> 3;
            byte[] iv = new byte[nBytes];
            for (int i = 0; i < iv.Length; i++)
                iv[i] = encryptedData[i];

            using (var bobAlgorithm = new ECDiffieHellmanCng(bobKey))
            using (CngKey alicePubKey = CngKey.Import(alicePubKeyBlob,
                  CngKeyBlobFormat.EccPublicBlob))
            {
                byte[] symmKey = bobAlgorithm.DeriveKeyMaterial(alicePubKey);
                Console.WriteLine("Bob creates this symmetric key with " +
                      "Alices public key information: {0}",
                      Convert.ToBase64String(symmKey));

                aes.Key = symmKey;
                aes.IV = iv;

                using (ICryptoTransform decryptor = aes.CreateDecryptor())
                using (MemoryStream ms = new MemoryStream())
                {
                    var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write);
                    cs.Write(encryptedData, nBytes, encryptedData.Length - nBytes);
                    cs.Close();

                    rawData = ms.ToArray();

                    Console.WriteLine("Bob decrypts message to: {0}",
                          Encoding.UTF8.GetString(rawData));
                }
                aes.Clear();

                return rawData;
            }
        }
    }
}

Client code (Alice) :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Security.Cryptography;
using System.IO;

namespace ClientAlice
{
    class Program
    {
        static CngKey aliceKey;  // client private key

        static byte[] alicePubKeyBlob; // client public key
        static byte[] bobPubKeyBlob;   // server public key
        static byte[] symetricKey;     // the symetric key that we want to give to the server securely

        static void Main(string[] args)
        {
            //create the client private and public keys
            CreateKeys();

            // initialice the server's public key we will need it later
            bobPubKeyBlob = new byte[alicePubKeyBlob.Length];

            // connect to the server and open a stream in order to comunicate with it
            TcpClient alice = new TcpClient("192.168.0.120", 54540);
            var stream = alice.GetStream();

            // send to the server the public key (client's public key)
            stream.Write(alicePubKeyBlob, 0, alicePubKeyBlob.Length);

            // now wait to receive the server's public key
            stream.Read(bobPubKeyBlob, 0, bobPubKeyBlob.Length);

            // create a random symetric key
            symetricKey = new byte[1000];
            Random r = new Random();
            r.NextBytes(symetricKey);

            // Encrypt the symetric key with the server's public key
            byte[] encrytpedData = AliceSendsData(symetricKey);

            // once encrypted send that encrypted data to the server. The only one that is going to be able to unecrypt that will be the server
            stream.Write(encrytpedData, 0, encrytpedData.Length);

            // not the server and client should have the same symetric key
        }


        private static void CreateKeys()
        {
            aliceKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256);
            alicePubKeyBlob = aliceKey.Export(CngKeyBlobFormat.EccPublicBlob);
        }

        private static byte[] AliceSendsData(byte[] rawData)
        {

            byte[] encryptedData = null;

            using (var aliceAlgorithm = new ECDiffieHellmanCng(aliceKey))
            using (CngKey bobPubKey = CngKey.Import(bobPubKeyBlob,
                  CngKeyBlobFormat.EccPublicBlob))
            {
                byte[] symmKey = aliceAlgorithm.DeriveKeyMaterial(bobPubKey);
                Console.WriteLine("Alice creates this symmetric key with " +
                      "Bobs public key information: {0}",
                      Convert.ToBase64String(symmKey));

                using (var aes = new AesCryptoServiceProvider())
                {
                    aes.Key = symmKey;
                    aes.GenerateIV();
                    using (ICryptoTransform encryptor = aes.CreateEncryptor())
                    using (MemoryStream ms = new MemoryStream())
                    {
                        // create CryptoStream and encrypt data to send
                        var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write);

                        // write initialization vector not encrypted
                        ms.Write(aes.IV, 0, aes.IV.Length);
                        cs.Write(rawData, 0, rawData.Length);
                        cs.Close();
                        encryptedData = ms.ToArray();
                    }
                    aes.Clear();
                }
            }
            Console.WriteLine("Alice: message is encrypted: {0}",
                  Convert.ToBase64String(encryptedData)); ;
            Console.WriteLine();
            return encryptedData;
        }
    }
}

perhaps I should use the ssl connection. I think we learn a lot when creating this kind of programs. I want to know if this technique will be a secure way of Alice giving a symmetric key to Bob in a secure way.


Solution

  • Your code is subject to man-in-the-middle attacks. SSL addresses this problem by relying on trusted third-party and validating the public key by checking the certificate chain up to the trusted root. So your best option is to (a) take SSL and use it, and (b) learn by reading security books (first) rather than by implementing your own algorithms.