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
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 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With