Secure messages in NodeJS using ECDH

Perry Mitchell / 2016-11-12 17:02:01
Secure messages in NodeJS using ECDH

Part of net­work­ing be­tween ap­pli­ca­tions is know­ing how to get your data to the other ap­pli­ca­tion with­out it be­ing in­ter­cepted. HTTPS is a great ex­am­ple of a widely used pro­to­col utilised for trans­fer se­cu­rity. Messages are en­crypted and se­curely shared be­tween both par­ties.

There are a va­ri­ety of meth­ods, rang­ing in both dif­fi­culty to im­ple­ment (properly) and se­cure­ness, that we could use in a NodeJS ap­pli­ca­tion to en­crypt mes­sages be­tween ap­pli­ca­tions. Before we dive into one of these, I’d like to men­tion two play­ers in the crypto game that are of­ten used to­gether, but per­form very dif­fer­ent op­er­a­tions.

AES and Diffie-Hellman

AES is a data en­cryp­tion spec­i­fi­ca­tion that is com­monly used to en­crypt all sorts of sen­si­tive data. AES has sev­eral modes of op­er­a­tion (CBC, CTR, CFB) that can some­times be more use­ful in cer­tain use cases. AES al­lows us to en­crypt data us­ing a key (simplified ex­pla­na­tion), and to even­tu­ally de­crypt the same data us­ing the same key.

Diffie-Hellman is a key ex­change method used for se­curely gen­er­at­ing se­cret keys be­tween 2 par­ties. Diffie-Hellman (DH) does not per­form any en­cryp­tion, and re­lies on the 2 par­ties to share their pub­lic keys be­tween each other.

Here’s a sim­ple Alice and Bob di­a­gram that should help demon­strate what DH is all about:

Diffie-Hellman key generation

  1. Alice gen­er­ates a pub­lic/​pri­vate key-pair us­ing DH
  2. Bob gen­er­ates his own pub­lic/​pri­vate key-pair us­ing DH
  3. Alice gives her pub­lic key to Bob (publically vis­i­ble)
  4. Bob gives his pub­lic key to Alice (publically vis­i­ble)
  5. Alice and Bob use each oth­er’s pub­lic keys with their pri­vate keys to gen­er­ate se­cret keys
  6. Both se­cret keys are iden­ti­cal

This is what makes DH so spe­cial - be­ing able to trans­fer pub­lic keys in broad day­light whilst main­tain­ing se­cu­rity. The se­cret keys are not vis­i­ble to any­one other than the two ex­chang­ing par­ties.

Putting it to­gether

So AES re­quires se­cret keys to be used to en­crypt and de­crypt some data, and DH is a tech­nique used to gen­er­ate se­cret keys that no one else knows about (it is im­por­tant to re­mem­ber here that nei­ther one of these tools al­lows one to ver­ify the other party is who they say they are). Putting them to­gether, DH could be used to gen­er­ate the se­cret en­cryp­tion keys used in AES en­cryp­tion and de­cryp­tion. One party (after hav­ing their DH key gen­er­ated) could en­crypt (using AES) some mes­sage and send it se­curely to the other party, where they will de­crypt it us­ing their se­cret key (and AES).


Diffie-Hellman comes in an­other va­ri­ety: ECDH (Elliptic-Curve Diffie-Hellman). ECDH sim­ply re­duces the com­pu­ta­tional re­quire­ments of the process, and is my choice for demon­stra­tion here. You’ll most likely find more im­ple­men­ta­tions of ECDH over DH any­how.

Practical ex­am­ple

Let’s walk through our Alice and Bob ex­am­ple above: We have two par­ties that want to send some in­for­ma­tion be­tween each other se­curely.

Let’s build this setup in NodeJS - start­ing with Alice:

const crypto = require("crypto");

let aliceECDH = crypto.createECDH("secp256k1");

let alicePublicKey = aliceECDH.getPublicKey(null, "compressed"),
    alicePrivateKey = aliceECDH.getPrivateKey(null, "compressed");

console.log("Alice Public: ", alicePublicKey.length, alicePublicKey.toString("hex"));
console.log("Alice Private:", alicePrivateKey.length, alicePrivateKey.toString("hex"));

Now we can re­peat the en­tire process for Bob:

let bobECDH = crypto.createECDH("secp256k1");

let bobPublicKey = bobECDH.getPublicKey(null, "compressed"),
    bobPrivateKey = bobECDH.getPrivateKey(null, "compressed");

console.log("Bob Public:   ", bobPublicKey.length, bobPublicKey.toString("hex"));
console.log("Bob Private:  ", bobPrivateKey.length, bobPrivateKey.toString("hex"));

At this stage the keys are swapped, po­ten­tially via some re­quest or socket con­nec­tion. Once both par­ties have the oth­er’s pub­lic key, they can gen­er­ate the se­cret keys:

// On Alice's side
let secret = aliceECDH.computeSecret(bobPublicKey);
console.log("Alice Secret: ", secret.length, secret.toString("hex"));
// On Bob's side
let secret = bobECDH.computeSecret(alicePublicKey);
console.log("Bob Secret:   ", secret.length, secret.toString("hex"));

And you end up with some­thing like the fol­low­ing:

Alice Public:  33 0220895a53cd561e7cbe490ca6e4aab1c87ea87833f877f1e34fb97e31f68c3b64
Alice Private: 32 f02ffd667a51742f0269a84e5696072ae5c9a5eb6078e7d449a1bf5089bcf41c
Bob Public:    33 02489b117270028ec97730644b636fcf382826feb601bdea5b34d16c1c30319470
Bob Private:   32 7e7ac01d88e427c47ba8bce46c5e87873c9d1d45ce5cb446d4c16a931338e3c6
Alice Secret:  32 984263961d59c9687b14dc60ada7be1b8cab36b7303e4e68f3720e7a71a3c939
Bob Secret:    32 984263961d59c9687b14dc60ada7be1b8cab36b7303e4e68f3720e7a71a3c939

Note that both se­cret keys are iden­ti­cal. Because their pri­vate keys were not shared, no one else can gen­er­ate the same se­cret keys.

Now that both par­ties have some se­cret they can use for en­cryp­tion, let’s get down to send­ing some se­cure data. I’ll use my own li­brary io­cane for the en­cryp­tion/​de­cryp­tion (wrapper for AES). Let’s en­crypt a mes­sage from Alice to Bob:

const iocane = require("iocane").crypto;

    .encryptWithPassword("Hi there, Bob!", secret1)
    .then(function(encrypted) {
        // send to Bob

You’ll get out­put like the fol­low­ing (Alice’s en­crypted mes­sage to Bob):


After send­ing it to Bob, he can de­crypt it us­ing the same se­cret key:

    .decryptWithPassword(encrypted, secret1)
    .then(function(message) {
        console.log(message); // "Hi there, Bob!"

And there we have it! Secure, en­crypted data trans­fer with key ex­chang­ing. I’ve in­cluded the full ex­am­ple be­low in a sin­gle file: