Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#
Article

Public/Private Key Encrypted Messenger

Rate me:
Please Sign up or sign in to vote.
4.69/5 (5 votes)
17 Sep 20074 min read 55.9K   1.7K   63   12
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.

C#
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

C#
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

C#
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()

C#
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()

C#
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.

C#
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

C#
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 bit4 bitsX bits4 bitsEND or NEXT
Type packet; could be encrypted or unencrypted (1 or 0)Length of data packet that followsData packetLength 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:

C#
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:

C#
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


Written By
Belgium Belgium
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMan-in-the-middle attack &amp; bandwidth Pin
Damage_Inc18-Sep-07 1:15
Damage_Inc18-Sep-07 1:15 
GeneralRe: Man-in-the-middle attack &amp; bandwidth Pin
Michael Demeersseman9-Oct-07 0:22
Michael Demeersseman9-Oct-07 0:22 
GeneralRe: Man-in-the-middle attack &amp; bandwidth Pin
Damage_Inc9-Oct-07 7:47
Damage_Inc9-Oct-07 7:47 
GeneralRe: Man-in-the-middle attack &amp; bandwidth Pin
Mihailo15-Oct-07 5:46
Mihailo15-Oct-07 5:46 
GeneralRe: Man-in-the-middle attack &amp; bandwidth Pin
Michael Demeersseman15-Oct-07 6:06
Michael Demeersseman15-Oct-07 6:06 
GeneralRe: Man-in-the-middle attack &amp; bandwidth Pin
Mihailo15-Oct-07 6:19
Mihailo15-Oct-07 6:19 
GeneralRe: Man-in-the-middle attack &amp;amp;amp; bandwidth [modified] Pin
Damage_Inc15-Oct-07 6:22
Damage_Inc15-Oct-07 6:22 
What Mihailo alludes to in his post is essentially SSL. Both client machines have a trusted certificate authority (e.g. Verisign, Thawte). When the client (you) connects to a server (www.paypal.com), it responds with a certificate containing its publickey, signed by the certificate authority. The client then validates the server's certificate using the certificate authority's public key. The client can then be assured that the server's certificate has not been tampered with and the public key is valid.

At this point the the client can send the symmetric key for the session as it can be assured of the server's identity. The secret lies in the fact that the certificate authority's public key is already on the client's machine and never flows across the wire. I believe that if you were to modify your code to follow a similar system, it wouldn't be susceptible to man-in-the-middle attacks. The problem however is that now you get into scenarios like replay-attacks and who knows what else. The fact remains that, yes, this is a great project for showing the features of public/private cryptography but you should never think of making use of security systems that are not open and peer-reviewed. I feel it is important to highlight these facts lest other programmers think this is a good idea for production systems.
GeneralRe: Man-in-the-middle attack &amp;amp;amp; bandwidth Pin
Michael Demeersseman15-Oct-07 6:46
Michael Demeersseman15-Oct-07 6:46 
GeneralRe: Man-in-the-middle attack &amp; bandwidth Pin
Damage_Inc15-Oct-07 6:12
Damage_Inc15-Oct-07 6:12 
GeneralRe: Man-in-the-middle attack &amp; bandwidth Pin
Mihailo15-Oct-07 6:35
Mihailo15-Oct-07 6:35 
QuestionMultiple Clients? Pin
Jim Weiler17-Sep-07 7:18
Jim Weiler17-Sep-07 7:18 
AnswerRe: Multiple Clients? Pin
Michael Demeersseman17-Sep-07 8:29
Michael Demeersseman17-Sep-07 8:29 

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

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