diff --git a/README.md b/README.md index eb90107..322d88a 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,24 @@ Generate a RSA PEM key pair from pure JS ```js var keypair = require('keypair'); -var pair = keypair(); -console.log(pair); +var syncPair = keypair(); +console.log('Synchronously generated keypair') +console.log(syncPair); + +keypair(function(asyncPair) { + console.log('Asynchronously generated keypair'); + console.log(asyncPair); +}); ``` outputs ``` $ node example.js +Synchronously generated keypair +{ public: '-----BEGIN RSA PUBLIC KEY-----\r\nMIGJAoGBAM3CosR73CBNcJsLv5E90NsFt6qN1uziQ484gbOoule8leXHFbyIzPQRozgEpSpi\r\nwhr6d2/c0CfZHEJ3m5tV0klxfjfM7oqjRMURnH/rmBjcETQ7qzIISZQ/iptJ3p7Gi78X5ZMh\r\nLNtDkUFU9WaGdiEb+SnC39wjErmJSfmGb7i1AgMBAAE=\r\n-----END RSA PUBLIC KEY-----\n', + private: '-----BEGIN RSA PRIVATE KEY-----\r\nMIICXAIBAAKBgQDNwqLEe9wgTXCbC7+RPdDbBbeqjdbs4kOPOIGzqLpXvJXlxxW8iMz0EaM4\r\nBKUqYsIa+ndv3NAn2RxCd5ubVdJJcX43zO6Ko0TFEZx/65gY3BE0O6syCEmUP4qbSd6exou/\r\nF+WTISzbQ5FBVPVmhnYhG/kpwt/cIxK5iUn5hm+4tQIDAQABAoGBAI+8xiPoOrA+KMnG/T4j\r\nJsG6TsHQcDHvJi7o1IKC/hnIXha0atTX5AUkRRce95qSfvKFweXdJXSQ0JMGJyfuXgU6dI0T\r\ncseFRfewXAa/ssxAC+iUVR6KUMh1PE2wXLitfeI6JLvVtrBYswm2I7CtY0q8n5AGimHWVXJP\r\nLfGV7m0BAkEA+fqFt2LXbLtyg6wZyxMA/cnmt5Nt3U2dAu77MzFJvibANUNHE4HPLZxjGNXN\r\n+a6m0K6TD4kDdh5HfUYLWWRBYQJBANK3carmulBwqzcDBjsJ0YrIONBpCAsXxk8idXb8jL9a\r\nNIg15Wumm2enqqObahDHB5jnGOLmbasizvSVqypfM9UCQCQl8xIqy+YgURXzXCN+kwUgHinr\r\nutZms87Jyi+D8Br8NY0+Nlf+zHvXAomD2W5CsEK7C+8SLBr3k/TsnRWHJuECQHFE9RA2OP8W\r\noaLPuGCyFXaxzICThSRZYluVnWkZtxsBhW2W8z1b8PvWUE7kMy7TnkzeJS2LSnaNHoyxi7Ia\r\nPQUCQCwWU4U+v4lD7uYBw00Ga/xt+7+UqFPlPVdz1yyr4q24Zxaw0LgmuEvgU5dycq8N7Jxj\r\nTubX0MIRR+G9fmDBBl8=\r\n-----END RSA PRIVATE KEY-----\n' } +Asynchronously generated keypair { public: '-----BEGIN RSA PUBLIC KEY-----\r\nMIGJAoGBAM3CosR73CBNcJsLv5E90NsFt6qN1uziQ484gbOoule8leXHFbyIzPQRozgEpSpi\r\nwhr6d2/c0CfZHEJ3m5tV0klxfjfM7oqjRMURnH/rmBjcETQ7qzIISZQ/iptJ3p7Gi78X5ZMh\r\nLNtDkUFU9WaGdiEb+SnC39wjErmJSfmGb7i1AgMBAAE=\r\n-----END RSA PUBLIC KEY-----\n', private: '-----BEGIN RSA PRIVATE KEY-----\r\nMIICXAIBAAKBgQDNwqLEe9wgTXCbC7+RPdDbBbeqjdbs4kOPOIGzqLpXvJXlxxW8iMz0EaM4\r\nBKUqYsIa+ndv3NAn2RxCd5ubVdJJcX43zO6Ko0TFEZx/65gY3BE0O6syCEmUP4qbSd6exou/\r\nF+WTISzbQ5FBVPVmhnYhG/kpwt/cIxK5iUn5hm+4tQIDAQABAoGBAI+8xiPoOrA+KMnG/T4j\r\nJsG6TsHQcDHvJi7o1IKC/hnIXha0atTX5AUkRRce95qSfvKFweXdJXSQ0JMGJyfuXgU6dI0T\r\ncseFRfewXAa/ssxAC+iUVR6KUMh1PE2wXLitfeI6JLvVtrBYswm2I7CtY0q8n5AGimHWVXJP\r\nLfGV7m0BAkEA+fqFt2LXbLtyg6wZyxMA/cnmt5Nt3U2dAu77MzFJvibANUNHE4HPLZxjGNXN\r\n+a6m0K6TD4kDdh5HfUYLWWRBYQJBANK3carmulBwqzcDBjsJ0YrIONBpCAsXxk8idXb8jL9a\r\nNIg15Wumm2enqqObahDHB5jnGOLmbasizvSVqypfM9UCQCQl8xIqy+YgURXzXCN+kwUgHinr\r\nutZms87Jyi+D8Br8NY0+Nlf+zHvXAomD2W5CsEK7C+8SLBr3k/TsnRWHJuECQHFE9RA2OP8W\r\noaLPuGCyFXaxzICThSRZYluVnWkZtxsBhW2W8z1b8PvWUE7kMy7TnkzeJS2LSnaNHoyxi7Ia\r\nPQUCQCwWU4U+v4lD7uYBw00Ga/xt+7+UqFPlPVdz1yyr4q24Zxaw0LgmuEvgU5dycq8N7Jxj\r\nTubX0MIRR+G9fmDBBl8=\r\n-----END RSA PRIVATE KEY-----\n' } ``` @@ -27,11 +37,11 @@ $ node example.js ## Performance Performance greatly depends on the bit size of the generated private key. With 1024 bits you get a key in 0.5s-2s, with 2048 bits it takes 8s-20s, on the same machine. As this will block the event loop while generating the key, -make sure that's ok or to spawn a child process or run it inside a webworker. +make sure that's ok or provide a callback to run asynchronously. ## API -### keypair([opts]) +### keypair([opts], [callback]) Get an RSA PEM key pair. @@ -40,6 +50,8 @@ Get an RSA PEM key pair. * `bits`: the size for the private key in bits. Default: **2048**. * `e`: the public exponent to use. Default: **65537**. +`callback` if provided makes `keypair` asynchronous. `callback` must be a function. + ## Installation With [npm](http://npmjs.org) do diff --git a/index.js b/index.js index 6be61c9..d5b0721 100644 --- a/index.js +++ b/index.js @@ -9,21 +9,44 @@ var util = forge.util = {}; * Expose `keypair`. */ -module.exports = function (opts) { +module.exports = function (opts, callback) { + var wrappedCallback; + + if (arguments.length === 1) { + if (typeof opts === 'function') { + callback = opts; + opts = undefined; + } + } + if (!opts) opts = {}; if (typeof opts.bits == 'undefined') opts.bits = 2048; - var keypair = forge.rsa.generateKeyPair(opts); - keypair = { - public: fix(forge.pki.publicKeyToRSAPublicKeyPem(keypair.publicKey, 72)), - private: fix(forge.pki.privateKeyToPem(keypair.privateKey, 72)) - }; - return keypair; + + if (callback) { + wrappedCallback = function generateKeyPairCallback(err, keypair) { + callback(err, fixKeypair(keypair)); + }; + } + + var keypair = forge.rsa.generateKeyPair(null, null, opts, wrappedCallback); + + // If we are async this will just return undefined. + return fixKeypair(keypair); }; function fix (str) { return str.replace(/\r/g, '') + '\n' } +function fixKeypair(keypair) { + if (!keypair) return; + + return { + public: fix(forge.pki.publicKeyToRSAPublicKeyPem(keypair.publicKey, 72)), + private: fix(forge.pki.privateKeyToPem(keypair.privateKey, 72)) + }; +} + /** * util.fillString */ @@ -4092,171 +4115,17 @@ function _generateKeyPair(state, options, callback) { options = {}; } - // web workers unavailable, use setImmediate - if(false || typeof(Worker) === 'undefined') { - function step() { - // 10 ms gives 5ms of leeway for other calculations before dropping - // below 60fps (1000/60 == 16.67), but in reality, the number will - // likely be higher due to an 'atomic' big int modPow - if(forge.pki.rsa.stepKeyPairGenerationState(state, 10)) { - return callback(null, state.keys); - } - forge.util.setImmediate(step); + function step() { + // 10 ms gives 5ms of leeway for other calculations before dropping + // below 60fps (1000/60 == 16.67), but in reality, the number will + // likely be higher due to an 'atomic' big int modPow + if(forge.pki.rsa.stepKeyPairGenerationState(state, 10)) { + return callback(null, state.keys); } - return step(); - } - - // use web workers to generate keys - var numWorkers = options.workers || 2; - var workLoad = options.workLoad || 100; - var range = workLoad * 30/8; - var workerScript = options.workerScript || 'forge/prime.worker.js'; - var THIRTY = new BigInteger(null); - THIRTY.fromInt(30); - var op_or = function(x,y) { return x|y; }; - generate(); - - function generate() { - // find p and then q (done in series to simplify setting worker number) - getPrime(state.pBits, function(err, num) { - if(err) { - return callback(err); - } - state.p = num; - getPrime(state.qBits, finish); - }); + forge.util.setImmediate(step); } - // implement prime number generation using web workers - function getPrime(bits, callback) { - // TODO: consider optimizing by starting workers outside getPrime() ... - // note that in order to clean up they will have to be made internally - // asynchronous which may actually be slower - - // start workers immediately - var workers = []; - for(var i = 0; i < numWorkers; ++i) { - // FIXME: fix path or use blob URLs - workers[i] = new Worker(workerScript); - } - var running = numWorkers; - - // initialize random number - var num = generateRandom(); - - // listen for requests from workers and assign ranges to find prime - for(var i = 0; i < numWorkers; ++i) { - workers[i].addEventListener('message', workerMessage); - } - - /* Note: The distribution of random numbers is unknown. Therefore, each - web worker is continuously allocated a range of numbers to check for a - random number until one is found. - - Every 30 numbers will be checked just 8 times, because prime numbers - have the form: - - 30k+i, for i < 30 and gcd(30, i)=1 (there are 8 values of i for this) - - Therefore, if we want a web worker to run N checks before asking for - a new range of numbers, each range must contain N*30/8 numbers. - - For 100 checks (workLoad), this is a range of 375. */ - - function generateRandom() { - var bits1 = bits - 1; - var num = new BigInteger(bits, state.rng); - // force MSB set - if(!num.testBit(bits1)) { - num.bitwiseTo(BigInteger.ONE.shiftLeft(bits1), op_or, num); - } - // align number on 30k+1 boundary - num.dAddOffset(31 - num.mod(THIRTY).byteValue(), 0); - return num; - } - - var found = false; - function workerMessage(e) { - // ignore message, prime already found - if(found) { - return; - } - - --running; - var data = e.data; - if(data.found) { - // terminate all workers - for(var i = 0; i < workers.length; ++i) { - workers[i].terminate(); - } - found = true; - return callback(null, new BigInteger(data.prime, 16)); - } - - // overflow, regenerate prime - if(num.bitLength() > bits) { - num = generateRandom(); - } - - // assign new range to check - var hex = num.toString(16); - - // start prime search - e.target.postMessage({ - e: state.eInt, - hex: hex, - workLoad: workLoad - }); - - num.dAddOffset(range, 0); - } - } - - function finish(err, num) { - // set q - state.q = num; - - // ensure p is larger than q (swap them if not) - if(state.p.compareTo(state.q) < 0) { - var tmp = state.p; - state.p = state.q; - state.q = tmp; - } - - // compute phi: (p - 1)(q - 1) (Euler's totient function) - state.p1 = state.p.subtract(BigInteger.ONE); - state.q1 = state.q.subtract(BigInteger.ONE); - state.phi = state.p1.multiply(state.q1); - - // ensure e and phi are coprime - if(state.phi.gcd(state.e).compareTo(BigInteger.ONE) !== 0) { - // phi and e aren't coprime, so generate a new p and q - state.p = state.q = null; - generate(); - return; - } - - // create n, ensure n is has the right number of bits - state.n = state.p.multiply(state.q); - if(state.n.bitLength() !== state.bits) { - // failed, get new q - state.q = null; - getPrime(state.qBits, finish); - return; - } - - // set keys - var d = state.e.modInverse(state.phi); - state.keys = { - privateKey: forge.pki.rsa.setPrivateKey( - state.n, state.e, d, state.p, state.q, - d.mod(state.p1), d.mod(state.q1), - state.q.modInverse(state.p)), - publicKey: forge.pki.rsa.setPublicKey(state.n, state.e) - }; - - callback(null, state.keys); - } + return step(); } /** diff --git a/test.js b/test.js index 88e49a0..760e8e5 100644 --- a/test.js +++ b/test.js @@ -7,5 +7,12 @@ test('keypair', function (t) { t.assert(/BEGIN RSA PRIVATE KEY/.test(pair.private), 'private header'); t.ok(pair.public, 'public key'); t.assert(/BEGIN RSA PUBLIC KEY/.test(pair.public), 'public header'); - t.end(); + + keypair(function(err, pair) { + t.ok(pair.private, 'private key'); + t.assert(/BEGIN RSA PRIVATE KEY/.test(pair.private), 'private header'); + t.ok(pair.public, 'public key'); + t.assert(/BEGIN RSA PUBLIC KEY/.test(pair.public), 'public header'); + t.end(); + }); });