End-to-end encrypted chat on JavaScript

As a starting point for adding end-to-end encryption, I’m going to choose minimalist chat written on bare .NET Core and JavaScript without any third party dependency. Let’s move from theory to implementation, but before that, I would like to give a bit of introduction in end-to-end encryption. What is it? What for? How?

Encryption is the process of encoding a message or information in such a way that only authorized parties can access it and those who are not authorized cannot — Wikipedia

End-to-end encryption is a system of communication where only the communicating users can read the messages — Wikipedia

Basically, a member of the chat encrypts a message before sending out, other members decrypts received the message. As a result, the chat server cannot see the content of the message. There are few ways on how to implement end-to-end encryption but I will focus on only one.

In order to achieve that I will make use of RSA algorithm. It is hard to explain how the algorithm works, more important is how to use it.

Each chat member gets 2 keys generated at the very start of room conversation. Let’s call them public key and private key . If a member encrypts a message using its public key then cipher can be decrypted only using its private key . This way, public key is sent out to other members. Eventually, all room members know other’s public keys , however, private key keeps being a secret.

Imagine, Alice, Bob and Charlie are chatting. Alice is sending a message. She encrypts the message using Bob’s public key and send. Then she encrypts the message using Charlie’s public key and send one more time. Bob and Charlie receive cipher and decrypt it using own private key .

Refactoring

Due to the limitation of RSA it cannot encrypt more than ~245 bytes. In order to break the limit, I’m going to use PGP it works practically the same way as RSA .

The beauty of modern browsers that they can perform cryptographic functions in JavaScript moreover this works pretty fast. I chose kbpgp.js by Keybase to enable PGP in JavaScript.

Server-side remains the same no changes needed. For client-side, first of all, I will wrap up low-level operations of kbpgp.js into high-level PgpKey class.

class PgpKey { constructor ( key ) { this . _key = key ; if ( this . canDecrypt ()) { this . _ring = new kbpgp . keyring . KeyRing (); this . _ring . add_key_manager ( key ); } } public () { return this . _key . armored_pgp_public ; } id () { return this . _key . get_pgp_short_key_id (); } nickname () { return this . _key . userids [ 0 ]. get_username (); } encrypt ( text , onDone ) { kbpgp . box ({ msg : text , encrypt_for : this . _key }, ( _ , cipher ) => onDone ( cipher )); } canDecrypt () { return this . _key . can_decrypt (); } decrypt ( cipher , onDone ) { kbpgp . unbox ({ keyfetch : this . _ring , armored : cipher , progress_hook : null }, ( _ , literals ) => onDone ( literals [ 0 ]. toString ())); } static generate ( nickname , onDone ) { let opt = { userid : nickname , primary : { nbits : 1024 }, subkeys : []}; kbpgp . KeyManager . generate ( opt , ( _ , key ) => key . sign ({}, () => key . export_pgp_public ({}, () => onDone ( new PgpKey ( key ))))); } static load ( publicKey , onDone ) { kbpgp . KeyManager . import_from_armored_pgp ({ armored : publicKey }, ( _ , key ) => onDone ( new PgpKey ( key ))); } }

let key = PgpKey.generate('John Doe' () => ...) generates new PGP key using RSA-1024 algorithm, which is more than enough to secure short lived chat conversation.

generates new PGP key using algorithm, which is more than enough to secure short lived chat conversation. key.public() returns public key as a string, for instance to send via wires.

returns public key as a string, for instance to send via wires. let key = PgpKey.load('-----BEGIN PGP PUBLIC KEY...', () => ...) loads PGP key from public key;

loads PGP key from public key; key.encrypt('Hi there!', cipher => ...) encrypts the text and returns cipher.

encrypts the text and returns cipher. key.decrypt('-----BEGIN PGP MESSAGE...', text => ...) decrypts the cipher and returns the text. Only PGP key having private key can decrypt.

1. PGP key generation

Before starting chat conversation PGP key will be generated for current member and their nickname. Since the nickname is included in generated PGP key anybody will know the key’s owner nickname.

2. Public key exchange & message reception

Once a new member is connected to a chat room it sends out generated public key. As a result, other participants respond with their public key. Eventually, all room members know other’s public keys .

As soon as a new message as cipher is received it must be decrypted by the generated key that of course includes private key.

3. Sending a message

At this stage, all public keys of room members are known. Before sending, a new message must be encrypted with the recipient’s public key, hence the message will be sent multiple times, one per member.

Result

Finally, app.gaevoy.com/cryptochat is ready to secure your conversation. Chrome Developer tools can show what server-side sees.

Take a look all changes in PR or source code I made for CryptoChat. Let me know what do you think.