End-to-end encryption between Node.js and webcrypto
Written by Jeroen van Veen on 30th April 2016
If Node C wants to send a chat message to node F, then it has to route all the way through node A and B (or through A-D-G-B-F) in order to arrive at node F. The node figures out the route by using Dijkstra’s shortest path algorithm. These routing messages can be used to exchange peer-to-peer web application data with, and are also useful to setup direct webRTC transports between browser nodes with. It then uses the overlay network as a decentralized signalling channel for webRTC SDP messages. Take for example the webRTC transport between node D and G. By using route D-A-B-G as a signalling channel, it’s quite straightforward to setup a connection between these browsers, which would otherwise not be aware of each other.
Now this is all fine and dandy, but node C doesn’t want node A and B to read a message that’s meant for node F! Then how do we get the message from C to F without A and B being able to read it? An option would be to use an encryption method like AES to scramble the message with a common secret key; the session key. AES is a fast and effective way to encrypt and decrypt larger messages with. But how do we get the session key to node F without node A and B learning about it? If A or B knows the session key, then encrypting the text wouldn’t make much sense anymore, because anybody with the session key can decrypt the message! As it turns out, this problem was already solved in the early 1970’s with the invention of asymmetric cryptography methods like RSA and Diffie Hellman key exchange.
Asymmetric cryptography uses two keys; a private key and a public key. A message from Alice to Bob can be encrypted by Alice using Bob’s public key. Bob receives the encrypted message from Alice and is the only one being able to decrypt it using his private key. Asymmetric cryptography is rather slow and has limitations on the encrypted message size, but is very useful to exchange a common secret, the AES session key, between Alice and Bob without having to fear that an MITM can read it as well.
After learning a bit more about cryptography methods, I initially thought I needed to create this RSA key exchange myself. On the Node.js side I initially used node-rsa to generate a key pair in Node.js. Using some webcrypto examples, I managed to encrypt some text using RSA-OAEP-256 (OAEP padding with SHA-256 hashing) and the Node.js generated public key. At that point, I thought I would be able to decrypt it in Node.js using the private key. Wrong! I couldn’t get it to work, because the node-rsa decrypt method kept on complaining about invalid OAEP padding. I tried other libraries like ursa to see if that would fix the problem, but it didn’t. After countless hours of messing around, not really knowing what I was doing, I finally found the reason. The used OAEP padding mechanism in node-rsa and in ursa’s OpenSSL bindings is hardcoded to use SHA1 hashing! Knowing this, I finally managed to decrypt the message using node-forge, which allowed me to use SHA-256 hashing for OAEP.
Diffie Hellman key exchange
Since then, a lot of people who know a thing or two about cryptography advised me not to ‘roll-my-own’ key exchange mechanism. So I continued reading and learned a bit about Diffie Hellman key exchange. DH is probably the right solution to safely exchange a common session key. With DH, Alice and Bob still exchange two public keys, just like with RSA. The difference is that with DH, Alice can derive the session key directly from her private key and Bob’s public key. Bob can do the same with his private key and Alice’s public key. They both end up with the same secret, without an MITM to be able to learn about it. This is better than ‘rolling-my-own’, since it doesn’t require to send the session key (encrypted) over the wire at all. Alice and Bob can from then on use the negotiated AES session key to encrypt follow-up messages. There is also the topic of message authentication, but I’ll leave that outside the scope of this article. Webcrypto allows using DH by using ECDH. Now I’m back at where I began! Generating an ECDH key pair with webcrypto is quite straightforward, but how to do the same thing in Node.js, and properly generate the session key with the right parameters?
Node.js built-in crypto library doesn’t resemble the webcrypto API at all. There are some third-party libs, but none of them explain how to interop with webcrypto. Happily I found the Subtle project, which aims to be a ‘WebCrypto Polyfill for node/browsers’. Awesome! This would make life on the Node.js side a lot easier, because then we can stick to the webcrypto API and perform the same operations in the browser and on Node.js. Bummer! It failed to compile on Node.js 5.x. Subtle depends on a compiled elliptic curve extension called ecc, which in turn depends on an older version of nan. After searching around for some hours about nan, I managed to get ecc working with nan 2.1.0. With Subtle up and running, I tested it to do the same RSA key exchange as described at the beginning of this post. Importing and exporting the keys was a breeze using spki/pkcs8, but now I got the same OAEP padding error again! (Error: Invalid RSAES-OAEP padding.]). This may have to do with the SHA1 hashing issue again. Finally, I tried ECDH key exchange between webcrypto and Node.js Subtle, but sadly this failed as well using my example code. Back to the drawing board…
The way to go?
I still have to figure out how to properly apply RSA encryption and ECDH key exchange using the Node.js Subtle library, as well do some more research on message authentication (digital signatures) and ephemeral ECDH. Overall, I feel that this Subtle library may be on the right track to make end-to-end encryption accessible for Node.js web developers without having to know all the ins and outs of cryptography. Another interesting direction to this may be the TweetNaCl-js project, but I didn’t try this yet. This is it for now! I need to read a lot more about the subject first, and then I’ll be able to write the next post, in which I will describe how to implement a complete end-to-end message encryption method.
If you enjoy this subject and want to know more, I found these resources very helpful when trying to learn a bit more about cryptography:
- An Overview of Cryptography – Gary C. Kessler
- ASN.1 key structures in DER and PEM – Paul Bakker
- Crypto 101 – lvh
- Cryptographic Right Answers – Colin Percival
- Coolaj86 blog archive – AJ ONeal
- Engelke blog archive – Charles Engelke
- Web Cryptography API Tutorial – Narayan Prusty
Do you know other resources which could be useful or have any other tips to help me on my quest, let me know in the comments below!