diff --git a/browser.js b/browser.js index c9847df..cff93ca 100644 --- a/browser.js +++ b/browser.js @@ -114,6 +114,40 @@ function hmacSha256Verify(key, msg, sig) { }); } +function trimBufferZeros(buf) { + var i = 0; + while (!buf[i] && i < buf.length) { + i += 1; + } + return buf.slice(i); +} + +function decryptWithDerivedKey(privateKey, opts, Px, trimZeros) { + if (trimZeros) { + Px = trimBufferZeros(Px); + } + // Tmp variable to save context from flat promises; + var encryptionKey; + return sha512(Px) + .then(function (hash) { + encryptionKey = hash.slice(0, 32); + var macKey = hash.slice(32); + var dataToMac = Buffer.concat([ + opts.iv, + opts.ephemPublicKey, + opts.ciphertext, + ]); + return hmacSha256Verify(macKey, dataToMac, opts.mac); + }) + .then(function (macGood) { + assert(macGood, "Bad MAC"); + return aesCbcDecrypt(opts.iv, encryptionKey, opts.ciphertext); + }) + .then(function (msg) { + return Buffer.from(new Uint8Array(msg)); + }); +} + /** * Generate a new valid private key. Will use the window.crypto or window.msCrypto as source * depending on your browser. @@ -202,7 +236,7 @@ var derive = exports.derive = function(privateKeyA, publicKeyB) { var keyA = ec.keyFromPrivate(privateKeyA); var keyB = ec.keyFromPublic(publicKeyB); var Px = keyA.derive(keyB.getPublic()); // BN instance - resolve(Buffer.from(Px.toArray())); + resolve(Buffer.from(Px.toArray(undefined, 32))); }); }; @@ -241,24 +275,14 @@ exports.encrypt = function(publicKeyTo, msg, opts) { }; exports.decrypt = function(privateKey, opts) { - // Tmp variable to save context from flat promises; - var encryptionKey; return derive(privateKey, opts.ephemPublicKey).then(function(Px) { - return sha512(Px); - }).then(function(hash) { - encryptionKey = hash.slice(0, 32); - var macKey = hash.slice(32); - var dataToMac = Buffer.concat([ - opts.iv, - opts.ephemPublicKey, - opts.ciphertext - ]); - return hmacSha256Verify(macKey, dataToMac, opts.mac); - }).then(function(macGood) { - assert(macGood, "Bad MAC"); - return aesCbcDecrypt(opts.iv, encryptionKey, opts.ciphertext); - }).then(function(msg) { - return Buffer.from(new Uint8Array(msg)); + return decryptWithDerivedKey(privateKey, opts, Px, false) + .catch(function(err) { + if (!Px[0]) { + return decryptWithDerivedKey(privateKey, opts, Px, true); + } + return Promise.reject(err); + }); }); }; diff --git a/index.js b/index.js index a63218b..3e1dd02 100644 --- a/index.js +++ b/index.js @@ -90,6 +90,33 @@ function pad32(msg){ } } +function trimBufferZeros(buf) { + var i = 0; + while (!buf[i] && i < buf.length) { + i += 1; + } + return buf.slice(i); +} + +function decryptWithDerivedKey(privateKey, opts, Px, trimZeros) { + if (trimZeros) { + Px = trimBufferZeros(Px); + } + assert(privateKey.length === 32, "Bad private key"); + assert(isValidPrivateKey(privateKey), "Bad private key"); + var hash = sha512(Px); + var encryptionKey = hash.slice(0, 32); + var macKey = hash.slice(32); + var dataToMac = Buffer.concat([ + opts.iv, + opts.ephemPublicKey, + opts.ciphertext, + ]); + var realMac = hmacSha256(macKey, dataToMac); + assert(equalConstTime(opts.mac, realMac), "Bad MAC"); + return aes256CbcDecrypt(opts.iv, encryptionKey, opts.ciphertext); +} + /** * Generate a new valid private key. Will use crypto.randomBytes as source. * @return {Buffer} A 32-byte private key. @@ -240,19 +267,15 @@ exports.encrypt = function(publicKeyTo, msg, opts) { * @return {Promise.} - A promise that resolves with the * plaintext on successful decryption and rejects on failure. */ -exports.decrypt = function(privateKey, opts) { - return derive(privateKey, opts.ephemPublicKey).then(function(Px) { - assert(privateKey.length === 32, "Bad private key"); - assert(isValidPrivateKey(privateKey), "Bad private key"); - var hash = sha512(Px); - var encryptionKey = hash.slice(0, 32); - var macKey = hash.slice(32); - var dataToMac = Buffer.concat([ - opts.iv, - opts.ephemPublicKey, - opts.ciphertext - ]); - var realMac = hmacSha256(macKey, dataToMac); - assert(equalConstTime(opts.mac, realMac), "Bad MAC"); return aes256CbcDecrypt(opts.iv, encryptionKey, opts.ciphertext); +exports.decrypt = function (privateKey, opts) { + return derive(privateKey, opts.ephemPublicKey).then(function (Px) { + try { + return decryptWithDerivedKey(privateKey, opts, Px, false); + } catch (err) { + if (!Px[0]) { + return decryptWithDerivedKey(privateKey, opts, Px, true); + } + throw err; + } }); }; diff --git a/package-lock.json b/package-lock.json index edf9c1b..937be4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "eccrypto", - "version": "1.1.5", + "version": "1.1.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b707dfb..5cca537 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eccrypto", - "version": "1.1.6", + "version": "1.1.7", "description": "JavaScript Elliptic curve cryptography library", "main": "index.js", "browser": "browser.js", diff --git a/test.js b/test.js index 4da5607..735c78a 100644 --- a/test.js +++ b/test.js @@ -23,6 +23,9 @@ privateKeyB.fill(3); var publicKeyB = eccrypto.getPublic(privateKeyB); var publicKeyBCompressed = eccrypto.getPublicCompressed(privateKeyB); +var privateKeyC = Buffer.from('bec50b320f17a60422a95619579675badf32c404289cb7bfa0b033b82ac17d98', 'hex'); +var publicKeyC = eccrypto.getPublic(privateKeyC); + describe("Key conversion", function() { it("should allow to convert private key to public", function() { expect(Buffer.isBuffer(publicKey)).to.be.true; @@ -161,6 +164,13 @@ describe("ECDH", function() { }); }); + it("should derive 32 byte long shared secret from privkey C and pubkey C", function() { + return eccrypto.derive(privateKeyC, publicKeyC).then(function(Px) { + expect(Buffer.isBuffer(Px)).to.be.true; + expect(Px.length).to.equal(32); + }); + }); + it("should reject promise on bad keys", function(done) { eccrypto.derive(Buffer.from("test"), publicKeyB).catch(function() { eccrypto.derive(publicKeyB, publicKeyB).catch(function() { @@ -253,6 +263,19 @@ describe("ECIES", function() { }); }); + it("should decrypt message with bad derived key", function() { + var sk = Buffer.from('2794d25fbfbd98c91182f3357f779d742c9cb87636d50c91159c3fc62e08a2fc', 'hex'); + var opts = { + iv: Buffer.from('2ab47869855480ae9f533d7a759e0bff', 'hex'), + ephemPublicKey: Buffer.from('0424a921276ecd28c2f752a5588fbbadefe978ea215998dc8baf6adb092bc22ecf2b01f908c2251383349e70c16fac6b5ba9c2e39775098fec70bbd0e0744b7cb8', 'hex'), + ciphertext: Buffer.from('98cf08e2152ecf53ce8294adb00d1deb', 'hex'), + mac: Buffer.from('38b895e9837b371d901f13b39cb0b8c09f27228935d98bf49e249368a6e2dd9f', 'hex'), + }; + return eccrypto.decrypt(sk, opts).then(function(msg) { + expect(msg.toString()).to.equal("hello world!"); + }); + }); + it("should reject promise on bad private key when decrypting", function(done) { eccrypto.encrypt(publicKeyA, Buffer.from("test")).then(function(enc) {