Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generating ECDH keys in the browser through WebCryptoAPI instead of the browserified node crypto module

I have a very tiny node script to create a public/private key Is there any way to do it on the client side without having to browserify hole crypto module?

var crypto    = require('crypto');

var userCurve = crypto.createECDH('prime256v1');
var userPublicKey = userCurve.generateKeys()
var userPrivateKey = userCurve.getPrivateKey();

I have tried this so far:

// https://github.com/diafygi/webcrypto-examples#ecdh---generatekey
window.crypto.subtle.generateKey(
    {
        name: "ECDH",
        namedCurve: "P-256", //can be "P-256", "P-384", or "P-521"
    },
    true, //whether the key is extractable (i.e. can be used in exportKey)
    ["deriveKey", "deriveBits"] //can be any combination of "deriveKey" and "deriveBits"
)
.then(function(key){
    //returns a keypair object
    console.log(key);
    console.log(key.publicKey);
    console.log(key.privateKey);
})
.catch(function(err){
    console.error(err);
});

But it looks nothing like the node version when i log it

like image 531
Endless Avatar asked Apr 13 '16 12:04

Endless


1 Answers

Let's do a complete elliptic curve Diffie-Hellman (ECDH) exchange to establish a shared secret between two parties. Alice uses Node.js and Bob sits at his browser (a recent version of Chrome or Firefox). (No need to browserify anything.)

(1) Alice generates a private and public key.

const crypto = require('crypto');

const alice = crypto.createECDH('prime256v1');
alice.generateKeys()

const alicePublicKey = alice.getPublicKey('hex')
const alicePrivateKey = alice.getPrivateKey('hex')

console.log(`publicKey: ${alicePublicKey}`)
console.log(`privateKey: ${alicePrivateKey}`)

Example output:

publicKey: 043a3770a8068738ded16c9409e1a6fbf6dde2360ac5b3fd3e5bb8d9fd6adaed6ea83ff5153f58ae13098e86da89df1beb14ef46388d3df76e8fe2ee0ff9e926d5
privateKey: 03ce9cb317c8761699f174943dc9b2d2b7991515b48216a4c677fcf5ee879f2c

(2) Alice sends her public key to Bob (043a3770...). Bob has written some helpers to convert hex strings to Uint8Arrays and buffers to hex strings.

const hex2Arr = str => {
    if (!str) {
        return new Uint8Array()
    }
    const arr = []
    for (let i = 0, len = str.length; i < len; i+=2) {
        arr.push(parseInt(str.substr(i, 2), 16))
    }
    return new Uint8Array(arr)
}

const buf2Hex = buf => {
    return Array.from(new Uint8Array(buf))
        .map(x => ('00' + x.toString(16)).slice(-2))
        .join('')
}

(3) Bob receives Alices's key and computes the shared secret

  • He generates his own private and public key
  • He exports his public key and sends it to Alice
  • He imports Alice's public key
  • He computes the shared secret using his private and Alice's public key

    // Alice's public key (received over an [insecure] connection)
    const alicePublicKeyHex = '043a3770a8068738ded16c9409e1a6fbf6dde2360ac5b3fd3e5bb8d9fd6adaed6ea83ff5153f58ae13098e86da89df1beb14ef46388d3df76e8fe2ee0ff9e926d5'
    const alicePublicKey = hex2Arr(alicePublicKeyHex)
    console.log(`Alice's publicKey: ${alicePublicKeyHex}`)
    
    let bob = null
    
    // generate Bob's private and public key
    window.crypto.subtle.generateKey(
        {
            name: 'ECDH',
            namedCurve: 'P-256'
        },
        false, // no need to make Bob's private key exportable
        ['deriveKey', 'deriveBits'])
        .then(bobKey => {
            bob = bobKey
            // export Bob's public key
            return window.crypto.subtle.exportKey(
                'raw', bobKey.publicKey
            )
        })
        .then(bobPublicKeyExported => {
            const bobPublicKeyHex = buf2Hex(bobPublicKeyExported)
            // display and send Bob's public key to Alice
            console.log(`Bob's publicKey: ${bobPublicKeyHex}`)
    
            // import Alice's public key
            return window.crypto.subtle.importKey(
                'raw',
                alicePublicKey,
                {
                    name: 'ECDH',
                    namedCurve: 'P-256'
                },
                true,
                [])
        })
        .then(aliceKeyImported => {
            // use Alice's imported public key and
            // Bob's private key to compute the shared secret
            return window.crypto.subtle.deriveBits(
                {
                    name: 'ECDH',
                    namedCurve: 'P-256',
                    public: aliceKeyImported
                },
                bob.privateKey,
                256)
        })
        .then(sharedSecret => {
            const sharedSecretHex = buf2Hex(sharedSecret)
            console.log(`sharedSecret: ${sharedSecretHex}`)
        })
        .catch(err => {
            console.log(err)
        })
    

Example output:

Alice's publicKey: 043a3770a8068738ded16c9409e1a6fbf6dde2360ac5b3fd3e5bb8d9fd6adaed6ea83ff5153f58ae13098e86da89df1beb14ef46388d3df76e8fe2ee0ff9e926d5
Bob's publicKey: 04aeceba6ae783c9b705833c2fa8822281f47f6f36bc867e4d398fa7a744d4fc63a010cbce1e6c9ac8858ad376a24ee8551615560f01c8bb63c86335c046b18962
sharedSecret: c26c9f370f001a947d7fec4dc9282d3e9ea718e1de487eb4f6fa7d6f0a311b97

(4) Alice receives Bob's public key (04aece...). She computes the shared secret as well.

const crypto = require('crypto')
const alice = crypto.createECDH('prime256v1')

// Alice's privateKey (generated previously)
const alicePrivateKey = '937cdd11062b612ff3cb3e4a3c183254b9728b4c8c3a64de799ed196b672734b'

// Bob's publicKey transmitted to Alice
const bobPublicKey = '04aeceba6ae783c9b705833c2fa8822281f47f6f36bc867e4d398fa7a744d4fc63a010cbce1e6c9ac8858ad376a24ee8551615560f01c8bb63c86335c046b18962'

// set Alice's private key (not needed if continuing from (1))
alice.setPrivateKey(alicePrivateKey, 'hex')

const sharedSecret = alice.computeSecret(bobPublicKey, 'hex', 'hex')
console.log(`sharedSecret: ${sharedSecret}`)

Examle output:

sharedSecret: c26c9f370f001a947d7fec4dc9282d3e9ea718e1de487eb4f6fa7d6f0a311b97

The secret is shared (the same).

(5) The shared secret is typically used to derive a symmetric key for encrypting messages between Alice and Bob (and they communicated happily ever after).

Remarks:

  • Usually there is no need to display or export the private key. Alice would typically continue the computation of the shared secret from step (1) (and omit alice.setPrivateKey(alicePrivateKey, 'hex')).

  • As the shared secret is most often used to derive a symmetric key, there is window.crypto.subtle.deriveKey and deriveBits can be ommitted. deriveBits was used here to illustrate that Alice and Bob indeed agreed on a shared secret.

like image 165
user1924627 Avatar answered Sep 19 '22 08:09

user1924627