I had been planning this for a while, to strengthen my understanding of encryption and networking. The idea is a messaging service, where users can register and send messages which are encrypted in transit and in storage. I wanted this to be an app that could be accessed from anywhere, so I made it into a website using HTML, CSS and JS. I chose to write the server in node.js, as it will make JSON interop very easy, so the client and server can send stringified JSON that can be read using JSON.parse. It also means that the same library can be used on both the client and the server.
To start with, I chose an encryption library and set up basic socket communication between client and server. For encryption I chose JSEncrypt as this was one of the only ones I could find decent examples of in use. Once this was done, I set up communications using websockets. Websockets are exactly what they sound like: sockets that can be used in vanilla js (no libraries needed – they’re built in). I rolled these two functions into one class so that I could pass in a JSON object that then had part of it encrypted and sent easily.
In order for the client and server to communicate securely, they both needed to know a shared key that they can encrypt and decrypt data with, otherwise the communication can be read by others. Unfortunately, by sending the key across the websocket, it could be intercepted and read, making the encryption pointless. However, techniques such as RSA have been invented to get around this problem, which is what I chose to use.
RSA is an asymmetric cryptosystem that involves a public and private key. The public key is shared with everyone, and the private key is kept only to yourself. If you want to send information to someone privately, you encrypt your message with their public key and send it to you. Now, you can decrypt the message using your private key. Because only you know what the private key is, only you can read the message.
The RSA cryptosystem generates the two public and private keys (for more information on generation, see here – I found it very useful to understand). The server and client can then communicate using these. However, due to the size of the numbers involved, there is a limit on the size of information that can be sent using RSA, that is quite small. For 1024-bit keys, the message must be 117 bytes or fewer (the modulus is 128-bytes, minus 11 for the padding of the message) [source]. To get around this though, we can is use RSA to encrypt a second encryption key that is used for AES. The AES key can then be used for communication. The diagram below shows this.
Once the AES key has been acquired, there is no more need for the RSA keys. Messages can be securely sent between the client and server by encrypting and decrypting using the key. Messages could include things like login, register and simple chat messages themselves. Note that each AES key is unique to each session – if it wasn’t, you could connect to acquire a key and then listen to other peoples conversations using that same key.
Now this was done, it was time to start writing the server message handling. The server will listen to the websocket until it receives a request, at which point it will respond with a result telling the client whether the operation suceeded or not, and if not why. When the server receives a message request, it first decrypts it into JSON. It then looks at the target recipient. If they are currently using the application, the server sends that client a message containing the plaintext (but encrypted with the recipients agreed AES key) so that the application can update in real time. After this, the server stores the message in its database, so that clients can request their messages from the server even after disconnect.
For the database on the server, I used NeDB. NeDB is like a relational database of JSON objects that can be interacted with through a variety of asynchronous functions like insert, update, get and remove (similar to SQL). However, one of the more useful features of NeDB is that you can customise what happens before writing to the database and after fetching from it. This means that database information can be encrypted using a hidden server-only key, so even if the database is read it can’t be understood. There are several of these NeDB databases for things like users, messages and chats on the server.
The majority of the project was done now – I added registering capabilities using nodemailer to verify email addresses – although at the time of writing you cannot reset your password (I will add this soon). Finally, made the client side more user friendly.
Note: if you want to use the project, be aware that you may not connect on the first attempt (see status bar at the bottom). If this happens, wait 10 seconds and then press connect again. This happens because the server is asleep and needs to be woken up – the first connection attempt wakes it, and the second one can actually connect. Please beware that as of the time of writing, there is no forgot password menu (though you can email me if this becomes an issue). Also, encryption may not actually be secure – there may have been things I have missed, so don’t use this for important information 🙂
View project (project is currently down since replit has removed their free tier, am working on migration as of March 2024)