From cdcca5f82854f01dc5d9767b8fbfdde20ca1c087 Mon Sep 17 00:00:00 2001 From: Richard Schneider Date: Sun, 28 Jan 2018 07:54:04 +1300 Subject: [PATCH] feat: improve perf (#117) --- package.json | 2 +- src/keys/index.js | 15 ++++----- src/keys/rsa-class.js | 34 ++++++++++++------- src/pbkdf2.js | 27 +++++++-------- test/crypto.spec.js | 2 +- test/keys/rsa.spec.js | 76 ++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 117 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index 308e53e..407dd12 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,10 @@ "async": "^2.6.0", "browserify-aes": "^1.1.1", "bs58": "^4.0.1", - "jsrsasign": "^8.0.4", "keypair": "^1.0.1", "libp2p-crypto-secp256k1": "~0.2.2", "multihashing-async": "~0.4.7", + "node-forge": "^0.7.1", "pem-jwk": "^1.5.1", "protons": "^1.0.1", "rsa-pem-to-jwk": "^1.1.3", diff --git a/src/keys/index.js b/src/keys/index.js index 7712558..b0f576e 100644 --- a/src/keys/index.js +++ b/src/keys/index.js @@ -2,8 +2,7 @@ const protobuf = require('protons') const keysPBM = protobuf(require('./keys.proto')) -const jsrsasign = require('jsrsasign') -const KEYUTIL = jsrsasign.KEYUTIL +const forge = require('node-forge') exports = module.exports @@ -120,13 +119,13 @@ exports.marshalPrivateKey = (key, type) => { exports.import = (pem, password, callback) => { try { - const key = KEYUTIL.getKey(pem, password) - if (key instanceof jsrsasign.RSAKey) { - const jwk = KEYUTIL.getJWKFromKey(key) - return supportedKeys.rsa.fromJwk(jwk, callback) - } else { - throw new Error(`Unknown key type '${key.prototype.toString()}'`) + const key = forge.pki.decryptRsaPrivateKey(pem, password) + if (key === null) { + throw new Error('Cannot read the key, most likely the password is wrong or not a RSA key') } + let der = forge.asn1.toDer(forge.pki.privateKeyToAsn1(key)) + der = Buffer.from(der.getBytes(), 'binary') + return supportedKeys.rsa.unmarshalRsaPrivateKey(der, callback) } catch (err) { callback(err) } diff --git a/src/keys/rsa-class.js b/src/keys/rsa-class.js index d1fcc2a..afaea88 100644 --- a/src/keys/rsa-class.js +++ b/src/keys/rsa-class.js @@ -6,7 +6,7 @@ const bs58 = require('bs58') const crypto = require('./rsa') const pbm = protobuf(require('./keys.proto')) -const KEYUTIL = require('jsrsasign').KEYUTIL +const forge = require('node-forge') const setImmediate = require('async/setImmediate') class RsaPublicKey { @@ -127,20 +127,29 @@ class RsaPrivateKey { format = 'pkcs-8' } - setImmediate(() => { - ensure(callback) + ensure(callback) + setImmediate(() => { let err = null let pem = null try { - const key = KEYUTIL.getKey(this._key) // _key is a JWK (JSON Web Key) + const buffer = new forge.util.ByteBuffer(this.marshal()) + const asn1 = forge.asn1.fromDer(buffer) + const privateKey = forge.pki.privateKeyFromAsn1(asn1) + if (format === 'pkcs-8') { - pem = KEYUTIL.getPEM(key, 'PKCS8PRV', password) + const options = { + algorithm: 'aes256', + count: 10000, + saltSize: 128 / 8, + prfAlgorithm: 'sha512' + } + pem = forge.pki.encryptRsaPrivateKey(privateKey, password, options) } else { err = new Error(`Unknown export format '${format}'`) } - } catch (e) { - err = e + } catch (_err) { + err = _err } callback(err, pem) @@ -150,6 +159,7 @@ class RsaPrivateKey { function unmarshalRsaPrivateKey (bytes, callback) { const jwk = crypto.utils.pkcs1ToJwk(bytes) + crypto.unmarshalPrivateKey(jwk, (err, keys) => { if (err) { return callback(err) @@ -175,18 +185,18 @@ function fromJwk (jwk, callback) { }) } -function generateKeyPair (bits, cb) { +function generateKeyPair (bits, callback) { crypto.generateKey(bits, (err, keys) => { if (err) { - return cb(err) + return callback(err) } - cb(null, new RsaPrivateKey(keys.privateKey, keys.publicKey)) + callback(null, new RsaPrivateKey(keys.privateKey, keys.publicKey)) }) } -function ensure (cb) { - if (typeof cb !== 'function') { +function ensure (callback) { + if (typeof callback !== 'function') { throw new Error('callback is required') } } diff --git a/src/pbkdf2.js b/src/pbkdf2.js index cd61a49..993595a 100644 --- a/src/pbkdf2.js +++ b/src/pbkdf2.js @@ -1,18 +1,18 @@ 'use strict' -const crypto = require('jsrsasign').CryptoJS +const forge = require('node-forge') /** - * Maps an IPFS hash name to its jsrsasign equivalent. + * Maps an IPFS hash name to its node-forge equivalent. * * See https://github.com/multiformats/multihash/blob/master/hashtable.csv * * @private */ const hashName = { - sha1: crypto.algo.SHA1, - 'sha2-256': crypto.algo.SHA256, - 'sha2-512': crypto.algo.SHA512 + sha1: 'sha1', + 'sha2-256': 'sha256', + 'sha2-512': 'sha512' } /** @@ -26,16 +26,17 @@ const hashName = { * @returns {string} - A new password */ function pbkdf2 (password, salt, iterations, keySize, hash) { - const opts = { - iterations: iterations, - keySize: keySize / 4, // convert bytes to words (32 bits) - hasher: hashName[hash] - } - if (!opts.hasher) { + const hasher = hashName[hash] + if (!hasher) { throw new Error(`Hash '${hash}' is unknown or not supported`) } - const words = crypto.PBKDF2(password, salt, opts) - return crypto.enc.Base64.stringify(words) + const dek = forge.pkcs5.pbkdf2( + password, + salt, + iterations, + keySize, + hasher) + return forge.util.encode64(dek) } module.exports = pbkdf2 diff --git a/test/crypto.spec.js b/test/crypto.spec.js index a52e260..b37e43c 100644 --- a/test/crypto.spec.js +++ b/test/crypto.spec.js @@ -13,7 +13,7 @@ describe('libp2p-crypto', function () { this.timeout(20 * 1000) let key before((done) => { - crypto.keys.generateKeyPair('RSA', 2048, (err, _key) => { + crypto.keys.generateKeyPair('RSA', 512, (err, _key) => { if (err) { return done(err) } diff --git a/test/keys/rsa.spec.js b/test/keys/rsa.spec.js index 69060ab..b46c5f7 100644 --- a/test/keys/rsa.spec.js +++ b/test/keys/rsa.spec.js @@ -19,7 +19,7 @@ describe('RSA', function () { let key before((done) => { - crypto.keys.generateKeyPair('RSA', 2048, (err, _key) => { + crypto.keys.generateKeyPair('RSA', 512, (err, _key) => { if (err) { return done(err) } @@ -97,7 +97,7 @@ describe('RSA', function () { }) it('not equals other key', (done) => { - crypto.keys.generateKeyPair('RSA', 2048, (err, key2) => { + crypto.keys.generateKeyPair('RSA', 512, (err, key2) => { if (err) { return done(err) } @@ -297,8 +297,42 @@ mBdkD5r+ixWF174naw53L8U9wF8kiK7pIE1N9TR4USEeovLwX6Ni/2MMDZedOfof }) }) - // AssertionError: expected 'this only supports TripleDES' to not exist - it.skip('can read a private encrypted key (v2 aes-256-cbc)', (done) => { + it('can read a private encrypted key (v2 aes-128-cbc)', (done) => { + /* + * Generated with + * openssl genpkey -algorithm RSA + * -pkeyopt rsa_keygen_bits:1024 + * -pkeyopt rsa_keygen_pubexp:65537 + * -out foo.pem + * openssl pkcs8 -in foo.pem -topk8 -v2 aes-128-cbc -passout pass:mypassword + */ + const pem = `-----BEGIN ENCRYPTED PRIVATE KEY----- +MIICzzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIP5QK2RfqUl4CAggA +MB0GCWCGSAFlAwQBAgQQj3OyM9gnW2dd/eRHkxjGrgSCAoCpM5GZB0v27cxzZsGc +O4/xqgwB0c/bSJ6QogtYU2KVoc7ZNQ5q9jtzn3I4ONvneOkpm9arzYz0FWnJi2C3 +BPiF0D1NkfvjvMLv56bwiG2A1oBECacyAb2pXYeJY7SdtYKvcbgs3jx65uCm6TF2 +BylteH+n1ewTQN9DLfASp1n81Ajq9lQGaK03SN2MUtcAPp7N9gnxJrlmDGeqlPRs +KpQYRcot+kE6Ew8a5jAr7mAxwpqvr3SM4dMvADZmRQsM4Uc/9+YMUdI52DG87EWc +0OUB+fnQ8jw4DZgOE9KKM5/QTWc3aEw/dzXr/YJsrv01oLazhqVHnEMG0Nfr0+DP +q+qac1AsCsOb71VxaRlRZcVEkEfAq3gidSPD93qmlDrCnmLYTilcLanXUepda7ez +qhjkHtpwBLN5xRZxOn3oUuLGjk8VRwfmFX+RIMYCyihjdmbEDYpNUVkQVYFGi/F/ +1hxOyl9yhGdL0hb9pKHH10GGIgoqo4jSTLlb4ennihGMHCjehAjLdx/GKJkOWShy +V9hj8rAuYnRNb+tUW7ChXm1nLq14x9x1tX0ciVVn3ap/NoMkbFTr8M3pJ4bQlpAn +wCT2erYqwQtgSpOJcrFeph9TjIrNRVE7Zlmr7vayJrB/8/oPssVdhf82TXkna4fB +PcmO0YWLa117rfdeNM/Duy0ThSdTl39Qd+4FxqRZiHjbt+l0iSa/nOjTv1TZ/QqF +wqrO6EtcM45fbFJ1Y79o2ptC2D6MB4HKJq9WCt064/8zQCVx3XPbb3X8Z5o/6koy +ePGbz+UtSb9xczvqpRCOiFLh2MG1dUgWuHazjOtUcVWvilKnkjCMzZ9s1qG0sUDj +nPyn +-----END ENCRYPTED PRIVATE KEY----- +` + crypto.keys.import(pem, 'mypassword', (err, key) => { + expect(err).to.not.exist() + expect(key).to.exist() + done() + }) + }) + + it('can read a private encrypted key (v2 aes-256-cbc)', (done) => { /* * Generated with * openssl genpkey -algorithm RSA @@ -333,6 +367,40 @@ DQd8 }) }) + it('can read a private encrypted key (v2 des)', (done) => { + /* + * Generated with + * openssl genpkey -algorithm RSA + * -pkeyopt rsa_keygen_bits:1024 + * -pkeyopt rsa_keygen_pubexp:65537 + * -out foo.pem + * openssl pkcs8 -in foo.pem -topk8 -v2 des -passout pass:mypassword + */ + const pem = `-----BEGIN ENCRYPTED PRIVATE KEY----- +MIICwzA9BgkqhkiG9w0BBQ0wMDAbBgkqhkiG9w0BBQwwDgQI0lXp62ozXvwCAggA +MBEGBSsOAwIHBAiR3Id5vH0u4wSCAoDQQYOrrkPFPIa0S5fQGXnJw1F/66g92Gs1 +TkGydn4ouabWb++Vbi2chee1oyZsN2l8YNzDi0Gb2PfjsGpg2aJk0a3/efgA0u6T +leEH1dA/7Hr9NVspgHkaXpHt3X6wdbznLYJeAelfj7sDXpOkULGWCkCst0Txb6bi +Oxv4c0yYykiuUrp+2xvHbF9c2PrcDb58u/OBZcCg3QB1gTugQKM+ZIBRhcTEFLrm +8gWbzBfwYiUm6aJce4zoafP0NSlEOBbpbr73A08Q1IK6pISwltOUhhTvspSZnK41 +y2CHt5Drnpl1pfOw9Q0svO3VrUP+omxP1SFP17ZfaRGw2uHd08HJZs438x5dIQoH +QgjlZ8A5rcT3FjnytSh3fln2ZxAGuObghuzmOEL/+8fkGER9QVjmQlsL6OMfB4j4 +ZAkLf74uaTdegF3SqDQaGUwWgk7LyualmUXWTBoeP9kRIsRQLGzAEmd6duBPypED +HhKXP/ZFA1kVp3x1fzJ2llMFB3m1JBwy4PiohqrIJoR+YvKUvzVQtbOjxtCEAj87 +JFnlQj0wjTd6lfNn+okewMNjKINZx+08ui7XANNU/l18lHIIz3ssXJSmqMW+hRZ9 +9oB2tntLrnRMhkVZDVHadq7eMFOPu0rkekuaZm9CO2vu4V7Qa2h+gOoeczYza0H7 +A+qCKbprxyL8SKI5vug2hE+mfC1leXVRtUYm1DnE+oet99bFd0fN20NwTw0rOeRg +0Z+/ZpQNizrXxfd3sU7zaJypWCxZ6TD/U/AKBtcb2gqmUjObZhbfbWq6jU2Ye//w +EBqQkwAUXR1tNekF8CWLOrfC/wbLRxVRkayb8bQUfdgukLpz0bgw +-----END ENCRYPTED PRIVATE KEY----- +` + crypto.keys.import(pem, 'mypassword', (err, key) => { + expect(err).to.not.exist() + expect(key).to.exist() + done() + }) + }) + it('can read a private encrypted key (v2 des3)', (done) => { /* * Generated with