Messaging services like WhatsApp keep bragging about end-to-end encryption (E2EE) telling us how it keeps our data safe even if messages we send are intercepted by a third party. Have you ever wondered how this works? how come they are able to secure our messages over the internet ? Well, I’ve asked myself those questions and I’ve studied it a little bit and implemented this E2EE using two different platforms and programming languages (an Android app with Kotlin and a back-end written in C#).
What is End to End Encryption (E2EE)?
I won’t reinvent the wheel by re-stating an already well-established definition here. Instead, I’ll quote a source on the internet.
End-to-end encryption (E2EE) is a method of secure communication that prevents third parties from accessing data while it’s transferred from one end system or device to another.
From Techtarget.com https://www.techtarget.com/searchsecurity/definition/end-to-end-encryption-E2EE
How does end-to-end Encryption work?
To understand how end-to-end encryption work, read follow the analogy below. Let’s say John wants to send a message to Daenerys. The message will travel in an unsecured medium, with untrusted people, but it should only be read by Daenerys. What should John do to securely send his message? The image below shows how John decides to send the message.
- Daenerys sends her special lock to John but keeps the key with her. This lock is very difficult to break, and only her key can open it.
- Everybody has access to the lock and can know who has the lock.
- John receives the lock and gets a very solid box that is very difficult to break.
- John writes the secret message on a piece of paper that he places in the solid box.
- John then locks the box with Daenerys’ lock and sends the locked box with the message in it to Daenerys.
- People could intercept the locked box, but won’t see through it, and can’t break it because both the box and the lock are very resistant.
- Once the box arrives at Daenerys, she uses her key and opens the box then reads the message.
The scenario above is very easy to understand right? It is exactly how End to End Encryption (E2EE) works. Except that, in E2EE,
- Daenerys’ key is her private key
- Daenerys’ lock is here public key
- The box is the encryption algorithm (RSA)
- And the message is, the information to be transmitted.
If you find this article useful, please follow me on Twitter,  Github, Linkedin, or like my Facebook page to stay updated.Follow me on social media and stay updated
Implementing End-to-End Encryption in Kotlin and C#
In the example below, we will make a very very simple implementation of E2EE with Kotlin as a client, and C# as the server.
NOTE: Implementing the following on environments with the same runtime environment and programming language is straightforward, but when two different runtimes are used it is kind of tricky. I’ll share tips below.
On the client, we generate the Public and Private key pairs in Kotlin.
1 2 3 4 5 6 7 8 | fun generateKey() { val keyGen = KeyPairGenerator.getInstance("RSA") keyGen.initialize(2048) val pair = keyGen.generateKeyPair() privateKey = pair.private publicKey = pair.public } |
In the next step, we need to send our public key to the server. The server will use the key to encrypt the data and send it back to the client.
Check this article about 10 things every junior software developer should know to be successful
Note that, the public key is principally made of, its exponent and the modulus. So, we will convert both to string and send only both via a post request to the C# back-end.
Note: The modulus and exponent should be sent as strings of BigIntegers, and not as Base64 strings as what is commonly done. Because converting the public key in Kotlin to Base64 will add an additional byte automatically. The C# runtime won’t know how to deal with this when encrypting data and it will cause the encryption not to work.
1 2 3 4 5 6 7 | var exponent = (CryptographyService.publicKey as RSAPublicKey).publicExponent var modulus = (CryptographyService.publicKey as RSAPublicKey).modulus val expoString = exponent.toString() var modulusString = modulus.toString() //TODO: Send the exponent string and modulus string to the C# back-end |
Once the C# back-end replies to our post request, we decrypt it with the private key we generated earlier and we have our message.
1 2 3 4 5 6 7 8 9 10 11 12 13 | //We receive the response from the API and convert it from Base64 to byte array var responseByte = java.util.Base64.getDecoder().decode(response.EncryptedKey) //Here is the method to decrypt the message fun decrypt2Mess(encryptedText: ByteArray, privatekey: RSAPrivateKey): String { val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding") cipher.init( Cipher.DECRYPT_MODE, privatekey ) var result = cipher.doFinal(encryptedText) return result; } |
As a result, you have your decrypted message, which wasn’t corrupted or decrypted along the way. This scenario is very basic, but imagine having to communicate with a peer. You’d send your public key to your peer, he encrypts the message he has to send to you, then you decrypt it once you receive it. There are other complex scenarios like encryption on blockchain networks etc.
Conclusion
I hope you now understand this concept of E2EE better. I’ve illustrated one of the most basic use case scenarios of E2EE above, and there are more complex ones, involving peer-to-peer communication, better management of keys, etc. I hope you found this useful. If you did, please don’t forget to like, follow me, and subscribe to notifications.
Follow me on social media and stay updated