From b0822932eb193eacf284cb8cd24038ad75f89e04 Mon Sep 17 00:00:00 2001 From: Fitch Li Date: Mon, 5 Feb 2024 13:52:03 +0800 Subject: [PATCH 1/5] fix bug for derived key length is not 32 in browser.js --- browser.js | 62 ++++++++++++++++++++++++++++++++--------------- index.js | 53 ++++++++++++++++++++++++++++------------ package-lock.json | 2 +- test.js | 10 ++++++++ 4 files changed, 92 insertions(+), 35 deletions(-) diff --git a/browser.js b/browser.js index c9847df..39a8406 100644 --- a/browser.js +++ b/browser.js @@ -36,7 +36,7 @@ function equalConstTime(b1, b2) { return false; } var res = 0; - for (var i = 0; i < b1.length; i++) { + for (var i = 0; i < b1.length; i += 1) { res |= b1[i] ^ b2[i]; // jshint ignore:line } return res === 0; @@ -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(Px.toBuffer(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..0d22ed3 100644 --- a/index.js +++ b/index.js @@ -72,7 +72,7 @@ function equalConstTime(b1, b2) { return false; } var res = 0; - for (var i = 0; i < b1.length; i++) { + for (var i = 0; i < b1.length; i += 1) { res |= b1[i] ^ b2[i]; // jshint ignore:line } return res === 0; @@ -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..3434678 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "eccrypto", - "version": "1.1.5", + "version": "1.1.6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/test.js b/test.js index 4da5607..f1ac9c7 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() { From c3176c7c3b651a0eacd785dd26fc7fc6d682d7f3 Mon Sep 17 00:00:00 2001 From: Fitch Li Date: Mon, 5 Feb 2024 14:14:07 +0800 Subject: [PATCH 2/5] add more tests --- test.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test.js b/test.js index f1ac9c7..735c78a 100644 --- a/test.js +++ b/test.js @@ -263,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) { From a9636eb55d2863692baaeb0b695cee2874d85b61 Mon Sep 17 00:00:00 2001 From: Fitch Li Date: Mon, 5 Feb 2024 14:16:56 +0800 Subject: [PATCH 3/5] bump version to 1.1.7 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3434678..937be4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "eccrypto", - "version": "1.1.6", + "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", From 0fd71a481dfca5d9aff028d86f1cd8d2326b117a Mon Sep 17 00:00:00 2001 From: Fitch Li Date: Mon, 5 Feb 2024 14:18:56 +0800 Subject: [PATCH 4/5] revert some changes --- browser.js | 2 +- index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/browser.js b/browser.js index 39a8406..0a07da9 100644 --- a/browser.js +++ b/browser.js @@ -36,7 +36,7 @@ function equalConstTime(b1, b2) { return false; } var res = 0; - for (var i = 0; i < b1.length; i += 1) { + for (var i = 0; i < b1.length; i++) { res |= b1[i] ^ b2[i]; // jshint ignore:line } return res === 0; diff --git a/index.js b/index.js index 0d22ed3..3e1dd02 100644 --- a/index.js +++ b/index.js @@ -72,7 +72,7 @@ function equalConstTime(b1, b2) { return false; } var res = 0; - for (var i = 0; i < b1.length; i += 1) { + for (var i = 0; i < b1.length; i++) { res |= b1[i] ^ b2[i]; // jshint ignore:line } return res === 0; From 711fcc8cd69564fd8d887bdea691bf9bbd968e8a Mon Sep 17 00:00:00 2001 From: Fitch Li Date: Wed, 7 Feb 2024 12:10:26 +0800 Subject: [PATCH 5/5] use Buffer.from in browser.js --- browser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser.js b/browser.js index 0a07da9..cff93ca 100644 --- a/browser.js +++ b/browser.js @@ -236,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(Px.toBuffer(undefined, 32)); + resolve(Buffer.from(Px.toArray(undefined, 32))); }); };