I started programming an encrypted chat client and server to learn more about encryption and how it’s implemented in network protocols (e.g. SSH or HTTPs over TLS). Now that it’s almost complete, I wanted to share what I learned with you.
I’m splitting up this series into two posts. The first, a.k.a. this one, will cover the concepts needed to understand how the program works. The second will dive into the code and connect these concepts with what the program is actually doing. Props to @oaktree for suggesting this format.
Let’s get started.
Part One: The Conceptual Overview
The objective is to establish a shared key for encrypting and decrypting messages sent between a client and a server.
To set up a chat room, a client must be able to securely send messages to a server—and vice versa. Symmetric encryption, which uses the same key for encrypting and decrypting messages, is a really good candidate for this task. However, to do so, the two parties must agree upon a key.
**Remember: encryption keys are just really big numbers
So how can we get two computers to have the same big number? We can’t just send it over the wire… that would defeat the purpose of encrypting in the first place, since an eavesdropper could just steal the key and decrypt everything. Instead, we’ll use something called the Diffie-Hellman Key Exchange.
Step 1: Diffie-Hellman Key Exchange
A really clever way of generating a shared key.
Diffie-Hellman (or DH for short) uses finite cyclic groups to have two entities come to the same number without ever explicitly sharing it. Many implementations use the multiplicative group of integers modulo p, where p is a prime number, for their finite cyclic group. However, other options such as elliptic curves also exist (you’re using one right now to view this webpage!).
Crazy maths aside, the actual concept behind the exchange isn’t all that hard to grasp, and the mathmatical operations necessary to perform it are grade-level too. Here’s the diagram listed on Wikipedia:
Illustration of the idea behind Diffie-Hellman Key Exchange
In our case, the “common paint” is a really large prime modulus p (at least 2048-bit) and prime base g. These values can be publicly shared without threatening our security. The “secret colors” are just random numbers generated by each participant and kept private. The whole thing works because of the following:
(g^a % p)^b % p = (g^b % p)^a % p
Where a and b are the “secret colors” and % is the modulus operator (which is just dividing and returning the remainder). Why are these expressions equal, you ask? I am not qualified to tell you. I suppose it’s sort of like asking why 2 + 2 = 4, except much harder to wrap your head around—it’s just the way it is. If anyone knows of a good explanation of this equation, please leave a comment as I’d love to understand it better myself. I haven’t studied group theory yet, but maybe someone who has can step in here.
It’s okay if you don’t fully understand conceptually what’s happening here, I’ll go over it in more detail when we look at the code. The important thing is we now have the same number: once we have that, we can move on to the next step.
Step 2: AES Encryption
The Advanced Encryption Standard
AES is a widely used symmetric encryption algorithm for its speed and security. Unless a system using it is leaking data (e.g. through a poor implementation), the only way to crack it is via brute force. We’ll be using AES-256 in Cipher Block Chaining mode, which relies on a 256-bit key (which is also why you’ll see we’ll be passing our shared big number through SHA-256 to standardize it to 256 bits in length) and causes each block in the series to rely on the previous one.
In this mode, we’ll have to start our messages with an initialization vector which serves as a starting block in our chain to scramble each successive block.
In CBC mode, each block of plaintext is XORed with the previous ciphertext block before being encrypted. This way, each ciphertext block depends on all plaintext blocks processed up to that point.
This is what the IV is for, as you cannot XOR a previous cipher-text if you haven’t even started ciphering
(Thanks to @pry0cc for this clarification)
Unlike the encryption key, this value can be shared publicly. It’s used to make reverse-engineering the key basically impossible, as encrypting “hello” twice with the same key but two different initialization vectors will come out completely different. But we have to be careful, reusing the same IV can degrade the security of our communications. See WEP for how this can go wrong.
We also need to add in some extra characters, or “padding”, so that our message is a multiple of the AES block size in length. Tagging on spaces to the end of the message is easy to strip on the other end, so we’ll use that. Thus, the first block will be our IV and each successive block will belong to our message, including any padding.
That’s it! We can now send encrypted messages back and forth between the server and the client. The client will submit encrypted chat messages to the server, which will then decrypt the messages and re-encrypt them with each client’s key that it will broadcast to. To anyone eavesdropping over the network, each message will look entirely different and none of the contents will be decipherable.
Wrapping It Up
I hope you enjoyed this conceptual overview of my encrypted chat program. Join me next time when we look over some key points of the code and see exactly how these concepts were implemented. The post should be up soon, but for now you’re welcome to take a sneak peek at the source on GitHub: https://github.com/spec-sec/SecureChat. If you have any suggestions or clarifications, please leave a comment. Thanks for reading!
Next: Part II