Click here to Skip to main content
Click here to Skip to main content

Public/Private Key Encrypted Messenger

, 17 Sep 2007
Rate this:
Please Sign up or sign in to vote.
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 Smile | :) .

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 byte<BitConverter.ToInt32(lengthbyte, 0)>;
        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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

No Biography provided

Comments and Discussions

 
GeneralMan-in-the-middle attack &amp; bandwidth PinmemberDamage_Inc18-Sep-07 1:15 
GeneralRe: Man-in-the-middle attack &amp; bandwidth PinmemberMichael Demeersseman9-Oct-07 0:22 
GeneralRe: Man-in-the-middle attack &amp; bandwidth PinmemberDamage_Inc9-Oct-07 7:47 
GeneralRe: Man-in-the-middle attack &amp; bandwidth PinmemberMihailo15-Oct-07 5:46 
GeneralRe: Man-in-the-middle attack &amp; bandwidth PinmemberMichael Demeersseman15-Oct-07 6:06 
GeneralRe: Man-in-the-middle attack &amp; bandwidth PinmemberMihailo15-Oct-07 6:19 
GeneralRe: Man-in-the-middle attack &amp;amp;amp; bandwidth [modified] PinmemberDamage_Inc15-Oct-07 6:22 
GeneralRe: Man-in-the-middle attack &amp;amp;amp; bandwidth PinmemberMichael Demeersseman15-Oct-07 6:46 
GeneralRe: Man-in-the-middle attack &amp; bandwidth PinmemberDamage_Inc15-Oct-07 6:12 
GeneralRe: Man-in-the-middle attack &amp; bandwidth PinmemberMihailo15-Oct-07 6:35 
QuestionMultiple Clients? PinmemberJim Weiler17-Sep-07 7:18 
AnswerRe: Multiple Clients? PinmemberMichael Demeersseman17-Sep-07 8:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 17 Sep 2007
Article Copyright 2007 by Michael Demeersseman
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid