65.9K
CodeProject is changing. Read more.
Home

Public/Private Key Encrypted Messenger

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (5 votes)

Sep 17, 2007

4 min read

viewsIcon

56881

downloadIcon

1681

An article about creating a public/private key encrypted internet messenger

Screenshot - SecureChat.png

Introduction

I was sitting in my garden with my laptop when suddenly a random idea popped up into my mind. I wanted to make some sort of messenger that could send messages encrypted over the internet. This is probably not the newest idea, but it does combine some technologies that are very interesting.

  1. Public private key encryption
  2. Delegates and events
  3. Thread-safe events (events that are called from other threads and have to make changes in the GUI)
  4. TCP-IP connection

The beauty of public/private key encryption is that everyone at any time may be sniffing your network activity, but they cannot understand what you are sending. This is because the public key can be received by anyone; the message encrypted with it can only be decrypted with the private key, which is never sent over the network/internet.

Using the Code

The program has 4 main classes:

  1. GUI class (form.cs)
  2. ChatSession class
  3. Message class
  4. Encryption class

GUI Class

This class is the main class of the program. This code initializes the ChatSession class, giving the current GUI control with it. One of the most popular features of a chat application is being able to see what the other person is trying to say. Because we will be waiting for the messages on another thread, we need the control to invoke a method to display a message. That way, we are on the thread-safe side.

public Form1()
{
    InitializeComponent();
    //the form control is sent with it because we will 

    //need it later to do invokes on it (to be on the safe side of threading)

    session = new ChatSession(this);
    SetEvents();
}

//These are our events which will be giving the GUI information.

void SetEvents()
{
    session.gotmessage += new ChatSession.gotMessage(session_gotmessage);
    session.lostconnection += new ChatSession.Lostconnection(
        session_lostconnection);      
}   

void session_gotmessage(string message)
{
    txttext.Text += DateTime.Now.ToString("hh:mm:ss") + ">" + 
        message + "\r\n"; 
}

Now that we've got our ChatSession class initialized, we need to connect to the other peer. One has to be the host and the other will connect to it.

Host

private void btnhost_Click(object sender, EventArgs e)
{    
    session.port = int.Parse(txtport.Text);
    MethodInvoker invoker = new MethodInvoker(session.Host);
    //starting to listen on another thread.

    invoker.BeginInvoke(null, null);
    
    btnhost.Enabled = false;
    btnconnect.Enabled = false;
}

Connect

private void btnconnect_Click(object sender, EventArgs e)
{
    session.port = int.Parse(txtport.Text);
    session.host = txtip.Text;
    MethodInvoker invoker = new MethodInvoker(session.Connect);
    //Trying to connect on another thread

    invoker.BeginInvoke(null, null);
    
    btnhost.Enabled = false;
    btnconnect.Enabled = false;
}

ChatSession Class

This class will handle the communication between the host and the client. The flow control is as follows:

  1. a) Host() waits for an incoming connection attempt
    b) Connect() tries to connect to the host
  2. Initialize() will initialize the necessary stuff
  3. a) getMessage() waits indefinitely for new messages and decrypts the messages
    b) SendMessage(string message) sends messages to the other peer and encrypts the messages

Host()

public void Host()
{
    try
    {
        TcpListener listener = new TcpListener(IPAddress.Any, port);
        listener.Start();
        SendMessageToControl("Starting to listen on port " + port);
        TcpClient client = listener.AcceptTcpClient();
        //this is blocked as long there is no connectien        

        SendMessageToControl("Connection established");
        Initialize(client);
        //When there is a connection all necessary streams have to be made.


    }
    catch (Exception ex)
    {
        MessageBox.Show("Unknown error occured:" + ex.Message);
    }
}

Connect()

public void Connect()
{
    try
    {
        TcpClient client = new TcpClient(host, port);
        SendMessageToControl("connecting to " + host + " on port " + port);
        Initialize(client);
    }
    catch
    {
        MessageBox.Show("Unknown error occured");
    }
}

Initialize()

This method does 4 things:

  1. Initializes the encryption object, which generates a public/private key pair
  2. Initializes the Message class to send and receive encrypted/unencrypted messages
  3. Starts listening on another thread for new messages
  4. Sends the public key to the other peer

The other peer is doing the exact the same thing. Both are waiting for messages and both will be sending their own public key, so both will be receiving the public key of their peer.

void Initialize(TcpClient client)
{
    encryption = new Encryption();          
    stream = client.GetStream();
    message = new Message(stream, encryption);
    //initialization of my message object        

    //starts accepting messages from the other peer

    MethodInvoker invoker = new MethodInvoker(getMessage); 
    invoker.BeginInvoke(null, null); // start listening for messages

    SendMessageToControl("Sending public key");
    //the public key has to be sent first (unencrypted of course)

    message.Send_Message(encryption.Get_This_Publickey(), false);
    SendMessageToControl("public key sent");
    connected = true;
}

SendMessageToControl

void SendMessageToControl(string message)
{
    control.BeginInvoke(gotmessage, new string[] { message });
}

Message Class

This class was a bit more tricky. To send the encrypted message, I stood with a dilemma. I could convert the bytes into a string and do a simple streamreader.writeLine or I could send the raw encrypted bytes.

If you use the first solution, first of all, your unencrypted message is converted to bytes. The bytes are encrypted and then converted to a string so you can do a streamwriter.writeLine. Then it's converted to bytes again by (I think) the transport OSI layer. After that, it's sent over the internet. The peer's transport layer converts it back to a string, which is then converted back to an array of bytes by your program. This is then decrypted and converted back to a string. That just doesn't sound logical :).

I found out that RSAcryptoprovider does not digest more than X number of elements in a byte array. If I want to send large text messages (more than 1 sentence) in one burst, I have to split my array of bytes, encrypt the split array and then send the split array to have it decrypted by the peer. Because we are dealing with random messages that could be encrypted or not, it's impossible to know how many bytes you are going to need. So, I thought of bringing in some robust protocol that could handle the transfer. It's as follows:

1 bit 4 bits X bits 4 bits END or NEXT
Type packet; could be encrypted or unencrypted (1 or 0) Length of data packet that follows Data packet Length of following packet ...

If the length of the following packet is 0, then you know that it's the end of the message. If it's an unencypted message, no splitting has to be done, so the whole message can be sent in one piece. This is the function that sends the messages:

public void Send_Message(string message, bool encrypt)
{
    
    byte[] bytemessage = ByteConverter.GetBytes(message);
    if (encrypt)
    {
        stream.WriteByte(1); 
        //1 for encrypted message, 0 for unencrypted message

        int maxlength = 50; 
        //I will break my array in pieces of 50 elements


        for (int i = 0, 
            bytestosend = bytemessage.Length; bytestosend>0; i++, 
            bytestosend-=maxlength)
        {
            int lengthbytestoencrypt=Math.Min(bytestosend,maxlength);

            byte[] bytestoencrypt = new byte[lengthbytestoencrypt];
            Buffer.BlockCopy(bytemessage, i * maxlength, bytestoencrypt, 
                0, lengthbytestoencrypt);

            byte[] encryptedbytestosend = 
                encryption.EncryptOutgoingV2(bytestoencrypt);
            sendSize(encryptedbytestosend.Length);
            stream.Write(encryptedbytestosend, 0, 
                encryptedbytestosend.Length);
        }
    }
    else
    {
        stream.WriteByte(0);
        //1 for encrypted message, 0 for unencrypted message

        sendSize(bytemessage.Length);
        stream.Write(bytemessage, 0, bytemessage.Length);
        
    }
    stream.Write(new byte[4],0,4);
    //writing four 0s not to confuse the receiving side, 

    //which is expecting 4 (4 bytes =int)

    stream.Flush();

}

This is the function that will be receiving the messages:

public string Receive_Message()
{
    byte encryptedbyte = (byte)stream.ReadByte(); 
    //is the message encrypted or not?

    byte[] lengthbyte = new byte[4]; 
    ArrayList receivedbytestotal = new ArrayList();

    for (; ; )
    {
        stream.Read(lengthbyte, 0, lengthbyte.Length);
        //reading the length of the next data burst

        if (BitConverter.ToInt32(lengthbyte, 0) == 0)
        //if its the end of the stream the stop

            break;
        byte[] messagebyte = new byte0)>;
        stream.Read(messagebyte, 0, messagebyte.Length);

        if (encryptedbyte == 1) messagebyte = 
            encryption.DecryptIncomingV2(messagebyte); 
        //if it's an encrypted burst, decrypt first

        receivedbytestotal.AddRange(messagebyte); 
        //adding the additional bytes to the array

    }
    
    byte[] messageinbytes=new byte[receivedbytestotal.Count];
    for (int i = 0; i < messageinbytes.Length; i++)
        messageinbytes[i] = (byte)receivedbytestotal[i];
    return ByteConverter.GetString(messageinbytes);

}

Points of Interest

  • TCP/IP sending controlled bytes over the internet
  • Public/private key encryption
  • Method invocation
  • Delegates and events

History

12 September 2007 - 1st release