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.
- Public private key encryption
- Delegates and events
- Thread-safe events (events that are called from other threads and have to make changes in the GUI)
- 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:
GUI
class (form.cs) ChatSession
class Message
class 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();
session = new ChatSession(this);
SetEvents();
}
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);
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);
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:
- a)
Host()
waits for an incoming connection attempt
b) Connect()
tries to connect to the host Initialize()
will initialize the necessary stuff - 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();
SendMessageToControl("Connection established");
Initialize(client);
}
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:
- Initializes the encryption object, which generates a public/private key pair
- Initializes the
Message
class to send and receive encrypted/unencrypted messages - Starts listening on another thread for new messages
- 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);
MethodInvoker invoker = new MethodInvoker(getMessage);
invoker.BeginInvoke(null, null);
SendMessageToControl("Sending public key");
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);
int maxlength = 50;
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);
sendSize(bytemessage.Length);
stream.Write(bytemessage, 0, bytemessage.Length);
}
stream.Write(new byte[4],0,4);
stream.Flush();
}
This is the function that will be receiving the messages:
public string Receive_Message()
{
byte encryptedbyte = (byte)stream.ReadByte();
byte[] lengthbyte = new byte[4];
ArrayList receivedbytestotal = new ArrayList();
for (; ; )
{
stream.Read(lengthbyte, 0, lengthbyte.Length);
if (BitConverter.ToInt32(lengthbyte, 0) == 0)
break;
byte[] messagebyte = new byte<BitConverter.ToInt32(lengthbyte, 0)>;
stream.Read(messagebyte, 0, messagebyte.Length);
if (encryptedbyte == 1) messagebyte =
encryption.DecryptIncomingV2(messagebyte);
receivedbytestotal.AddRange(messagebyte);
}
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
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.