![]() |
General Programming »
Cryptography & Security »
Security
Intermediate
Public/Private Key Encrypted MessengerBy Michael DemeerssemanAn article about creating a public/private key encrypted internet messenger |
C#, Windows, .NET 2.0, Visual Studio, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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.
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.
The program has 4 main classes:
GUI class (form.cs) ChatSession class Message class Encryption 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.
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;
}
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;
}
This class will handle the communication between the host and the client. The flow control is as follows:
Host() waits for an incoming connection attemptConnect() tries to connect to the host Initialize() will initialize the necessary stuff getMessage() waits indefinitely for new messages and decrypts the messagesSendMessage(string message) sends messages to the other peer and encrypts the messages 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);
}
}
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");
}
}
This method does 4 things:
Message class to send and receive encrypted/unencrypted messages 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;
}
void SendMessageToControl(string message)
{
control.BeginInvoke(gotmessage, new string[] { message });
}
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);
}
12 September 2007 - 1st release
| You must Sign In to use this message board. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 17 Sep 2007 Editor: Genevieve Sovereign |
Copyright 2007 by Michael Demeersseman Everything else Copyright © CodeProject, 1999-2009 Web20 | Advertise on the Code Project |