ECIES (Node)
This commit is contained in:
parent
4e1001a842
commit
1cf476250a
23
README.md
23
README.md
|
@ -74,6 +74,29 @@ eccrypto.derive(privateKeyA, publicKeyB).then(function(sharedKey1) {
|
||||||
### ECIES
|
### ECIES
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
var crypto = require("crypto");
|
||||||
|
var eccrypto = require("eccrypto");
|
||||||
|
|
||||||
|
var privateKeyA = crypto.randomBytes(32);
|
||||||
|
var publicKeyA = eccrypto.getPublic(privateKeyA);
|
||||||
|
var privateKeyB = crypto.randomBytes(32);
|
||||||
|
var publicKeyB = eccrypto.getPublic(privateKeyB);
|
||||||
|
|
||||||
|
// Encrypting the message for B.
|
||||||
|
eccrypto.encrypt(publicKeyB, Buffer("msg to b")).then(function(encrypted) {
|
||||||
|
// B decrypting the message.
|
||||||
|
eccrypto.decrypt(privateKeyB, encrypted).then(function(plaintext) {
|
||||||
|
console.log("Message to part B:", plaintext.toString());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Encrypting the message for A.
|
||||||
|
eccrypto.encrypt(publicKeyA, Buffer("msg to a")).then(function(encrypted) {
|
||||||
|
// A decrypting the message.
|
||||||
|
eccrypto.decrypt(privateKeyA, encrypted).then(function(plaintext) {
|
||||||
|
console.log("Message to part A:", plaintext.toString());
|
||||||
|
});
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
28
browser.js
28
browser.js
|
@ -35,14 +35,14 @@ function sha512(msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAes(op) {
|
function getAes(op) {
|
||||||
return function(iv, key, msg) {
|
return function(iv, key, data) {
|
||||||
var importAlgorithm = {name: "AES-CBC"};
|
var importAlgorithm = {name: "AES-CBC"};
|
||||||
var keyp = subtle.importKey("raw", key, importAlgorithm, false, [op]);
|
var keyp = subtle.importKey("raw", key, importAlgorithm, false, [op]);
|
||||||
return keyp.then(function(cryptoKey) {
|
return keyp.then(function(cryptoKey) {
|
||||||
var encAlgorithm = {name: "AES-CBC", iv: iv};
|
var encAlgorithm = {name: "AES-CBC", iv: iv};
|
||||||
return subtle[op](encAlgorithm, cryptoKey, msg);
|
return subtle[op](encAlgorithm, cryptoKey, data);
|
||||||
}).then(function(cipherText) {
|
}).then(function(result) {
|
||||||
return new Buffer(new Uint8Array(cipherText));
|
return new Buffer(new Uint8Array(result));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ exports.encrypt = function(publicKeyTo, msg, opts) {
|
||||||
assert(subtle, "WebCryptoAPI is not supported");
|
assert(subtle, "WebCryptoAPI is not supported");
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
// Tmp variables to save context from flat promises;
|
// Tmp variables to save context from flat promises;
|
||||||
var iv, ephemPublicKey, cipherText, macKey;
|
var iv, ephemPublicKey, ciphertext, macKey;
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function(resolve) {
|
||||||
var ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32);
|
var ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32);
|
||||||
ephemPublicKey = getPublic(ephemPrivateKey);
|
ephemPublicKey = getPublic(ephemPrivateKey);
|
||||||
|
@ -121,15 +121,15 @@ exports.encrypt = function(publicKeyTo, msg, opts) {
|
||||||
var encryptionKey = hash.slice(0, 32);
|
var encryptionKey = hash.slice(0, 32);
|
||||||
macKey = hash.slice(32);
|
macKey = hash.slice(32);
|
||||||
return aesCbcEncrypt(iv, encryptionKey, msg);
|
return aesCbcEncrypt(iv, encryptionKey, msg);
|
||||||
}).then(function(encrypted) {
|
}).then(function(data) {
|
||||||
cipherText = encrypted;
|
ciphertext = data;
|
||||||
var dataToMac = Buffer.concat([iv, ephemPublicKey, cipherText]);
|
var dataToMac = Buffer.concat([iv, ephemPublicKey, ciphertext]);
|
||||||
return hmacSha256Sign(macKey, dataToMac);
|
return hmacSha256Sign(macKey, dataToMac);
|
||||||
}).then(function(mac) {
|
}).then(function(mac) {
|
||||||
return {
|
return {
|
||||||
iv: iv,
|
iv: iv,
|
||||||
ephemPublicKey: ephemPublicKey,
|
ephemPublicKey: ephemPublicKey,
|
||||||
cipherText: cipherText,
|
ciphertext: ciphertext,
|
||||||
mac: mac,
|
mac: mac,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -137,7 +137,7 @@ exports.encrypt = function(publicKeyTo, msg, opts) {
|
||||||
|
|
||||||
exports.decrypt = function(privateKey, opts) {
|
exports.decrypt = function(privateKey, opts) {
|
||||||
assert(subtle, "WebCryptoAPI is not supported");
|
assert(subtle, "WebCryptoAPI is not supported");
|
||||||
// Tmp variables to save context from flat promises;
|
// Tmp variable to save context from flat promises;
|
||||||
var encryptionKey;
|
var encryptionKey;
|
||||||
return derive(privateKey, opts.ephemPublicKey).then(function(Px) {
|
return derive(privateKey, opts.ephemPublicKey).then(function(Px) {
|
||||||
return sha512(Px);
|
return sha512(Px);
|
||||||
|
@ -147,12 +147,12 @@ exports.decrypt = function(privateKey, opts) {
|
||||||
var dataToMac = Buffer.concat([
|
var dataToMac = Buffer.concat([
|
||||||
opts.iv,
|
opts.iv,
|
||||||
opts.ephemPublicKey,
|
opts.ephemPublicKey,
|
||||||
opts.cipherText
|
opts.ciphertext
|
||||||
]);
|
]);
|
||||||
return hmacSha256Verify(macKey, dataToMac, opts.mac);
|
return hmacSha256Verify(macKey, dataToMac, opts.mac);
|
||||||
}).then(function(goodMac) {
|
}).then(function(macGood) {
|
||||||
assert(goodMac, "Bad MAC");
|
assert(macGood, "Bad MAC");
|
||||||
return aesCbcDecrypt(opts.iv, encryptionKey, opts.cipherText);
|
return aesCbcDecrypt(opts.iv, encryptionKey, opts.ciphertext);
|
||||||
}).then(function(msg) {
|
}).then(function(msg) {
|
||||||
return new Buffer(new Uint8Array(msg));
|
return new Buffer(new Uint8Array(msg));
|
||||||
});
|
});
|
||||||
|
|
119
index.js
119
index.js
|
@ -8,18 +8,59 @@
|
||||||
var promise = typeof Promise === "undefined" ?
|
var promise = typeof Promise === "undefined" ?
|
||||||
require("es6-promise").Promise :
|
require("es6-promise").Promise :
|
||||||
Promise;
|
Promise;
|
||||||
|
var crypto = require("crypto");
|
||||||
// TODO(Kagami): We may fallback to pure JS implementation
|
// TODO(Kagami): We may fallback to pure JS implementation
|
||||||
// (`browser.js`) if this modules are failed to load.
|
// (`browser.js`) if this modules are failed to load.
|
||||||
var secp256k1 = require("secp256k1");
|
var secp256k1 = require("secp256k1");
|
||||||
var ecdh = require("./build/Release/ecdh");
|
var ecdh = require("./build/Release/ecdh");
|
||||||
|
|
||||||
|
function assert(condition, message) {
|
||||||
|
if (!condition) {
|
||||||
|
throw new Error(message || "Assertion failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sha512(msg) {
|
||||||
|
return crypto.createHash("sha512").update(msg).digest();
|
||||||
|
}
|
||||||
|
|
||||||
|
function aes256CbcEncrypt(iv, key, plaintext) {
|
||||||
|
var cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
|
||||||
|
var firstChunk = cipher.update(plaintext);
|
||||||
|
var secondChunk = cipher.final();
|
||||||
|
return Buffer.concat([firstChunk, secondChunk]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function aes256CbcDecrypt(iv, key, ciphertext) {
|
||||||
|
var cipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
|
||||||
|
var firstChunk = cipher.update(ciphertext);
|
||||||
|
var secondChunk = cipher.final();
|
||||||
|
return Buffer.concat([firstChunk, secondChunk]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hmacSha256(key, msg) {
|
||||||
|
return crypto.createHmac("sha256", key).update(msg).digest();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare two buffers in constant time to prevent timing attacks.
|
||||||
|
function equalConstTime(b1, b2) {
|
||||||
|
if (b1.length !== b2.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var res = 0;
|
||||||
|
for (var i = 0; i < b1.length; i++) {
|
||||||
|
res |= b1[i] ^ b2[i]; // jshint ignore:line
|
||||||
|
}
|
||||||
|
return res === 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the public key for a given private key.
|
* Compute the public key for a given private key.
|
||||||
* @param {Buffer} privateKey - A 32-byte private key
|
* @param {Buffer} privateKey - A 32-byte private key
|
||||||
* @return {Buffer} A 65-byte public key.
|
* @return {Buffer} A 65-byte public key.
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
exports.getPublic = secp256k1.createPublicKey;
|
var getPublic = exports.getPublic = secp256k1.createPublicKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an ECDSA signature.
|
* Create an ECDSA signature.
|
||||||
|
@ -50,13 +91,81 @@ exports.verify = function(publicKey, msg, sig) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derive shared secret for given private and public keys.
|
* Derive shared secret for given private and public keys.
|
||||||
* @param {Buffer} privateKeyA - Sender's private key
|
* @param {Buffer} privateKeyA - Sender's private key (32 bytes)
|
||||||
* @param {Buffer} publicKeyB - Recipient's public key
|
* @param {Buffer} publicKeyB - Recipient's public key (65 bytes)
|
||||||
* @return {Promise.<Buffer>} A promise that resolves with the derived
|
* @return {Promise.<Buffer>} A promise that resolves with the derived
|
||||||
* shared secret (Px) and rejects on bad key.
|
* shared secret (Px, 32 bytes) and rejects on bad key.
|
||||||
*/
|
*/
|
||||||
exports.derive = function(privateKeyA, publicKeyB) {
|
var derive = exports.derive = function(privateKeyA, publicKeyB) {
|
||||||
return new promise(function(resolve) {
|
return new promise(function(resolve) {
|
||||||
resolve(ecdh.derive(privateKeyA, publicKeyB));
|
resolve(ecdh.derive(privateKeyA, publicKeyB));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input/output structure for ECIES operations.
|
||||||
|
* @typedef {Object} Ecies
|
||||||
|
* @property {Buffer} iv - Initialization vector (16 bytes)
|
||||||
|
* @property {Buffer} ephemPublicKey - Ephemeral public key (65 bytes)
|
||||||
|
* @property {Buffer} ciphertext - The result of encryption (variable size)
|
||||||
|
* @property {Buffer} mac - Message authentication code (32 bytes)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt message for given recepient's public key.
|
||||||
|
* @param {Buffer} publicKeyTo - Recipient's public key (65 bytes)
|
||||||
|
* @param {Buffer} msg - The message being encrypted
|
||||||
|
* @param {?{?iv: Buffer, ?ephemPrivateKey: Buffer}} opts - You may also
|
||||||
|
* specify initialization vector (16 bytes) and ephemeral private key
|
||||||
|
* (32 bytes) to get deterministic results.
|
||||||
|
* @return {Promise.<Ecies>} - A promise that resolves with the ECIES
|
||||||
|
* structure on successful encryption and rejects on failure.
|
||||||
|
*/
|
||||||
|
exports.encrypt = function(publicKeyTo, msg, opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
// Tmp variable to save context from flat promises;
|
||||||
|
var ephemPublicKey;
|
||||||
|
return new promise(function(resolve) {
|
||||||
|
var ephemPrivateKey = opts.ephemPrivateKey || crypto.randomBytes(32);
|
||||||
|
ephemPublicKey = getPublic(ephemPrivateKey);
|
||||||
|
resolve(derive(ephemPrivateKey, publicKeyTo));
|
||||||
|
}).then(function(Px) {
|
||||||
|
var hash = sha512(Px);
|
||||||
|
var iv = opts.iv || crypto.randomBytes(16);
|
||||||
|
var encryptionKey = hash.slice(0, 32);
|
||||||
|
var macKey = hash.slice(32);
|
||||||
|
var ciphertext = aes256CbcEncrypt(iv, encryptionKey, msg);
|
||||||
|
var dataToMac = Buffer.concat([iv, ephemPublicKey, ciphertext]);
|
||||||
|
var mac = hmacSha256(macKey, dataToMac);
|
||||||
|
return {
|
||||||
|
iv: iv,
|
||||||
|
ephemPublicKey: ephemPublicKey,
|
||||||
|
ciphertext: ciphertext,
|
||||||
|
mac: mac,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt message using given private key.
|
||||||
|
* @param {Buffer} privateKey - A 32-byte private key of recepient of
|
||||||
|
* the mesage
|
||||||
|
* @param {Ecies} opts - ECIES structure (result of ECIES encryption)
|
||||||
|
* @return {Promise.<Buffer>} - 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) {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
59
test.js
59
test.js
|
@ -108,26 +108,24 @@ describe("ECDH", function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (typeof window !== "undefined") {
|
|
||||||
|
|
||||||
describe("ECIES", function() {
|
describe("ECIES", function() {
|
||||||
var ephemPrivateKey = Buffer(32);
|
var ephemPrivateKey = Buffer(32);
|
||||||
ephemPrivateKey.fill(4);
|
ephemPrivateKey.fill(4);
|
||||||
var ephemPublicKey = eccrypto.getPublic(ephemPrivateKey);
|
var ephemPublicKey = eccrypto.getPublic(ephemPrivateKey);
|
||||||
var iv = Buffer(16);
|
var iv = Buffer(16);
|
||||||
iv.fill(5);
|
iv.fill(5);
|
||||||
var cipherText = Buffer("bbf3f0e7486b552b0e2ba9c4ca8c4579", "hex");
|
var ciphertext = Buffer("bbf3f0e7486b552b0e2ba9c4ca8c4579", "hex");
|
||||||
var mac = Buffer("dbb14a9b53dbd6b763dba24dc99520f570cdf8095a8571db4bf501b535fda1ed", "hex");
|
var mac = Buffer("dbb14a9b53dbd6b763dba24dc99520f570cdf8095a8571db4bf501b535fda1ed", "hex");
|
||||||
var encOpts = {ephemPrivateKey: ephemPrivateKey, iv: iv};
|
var encOpts = {ephemPrivateKey: ephemPrivateKey, iv: iv};
|
||||||
var decOpts = {iv: iv, ephemPublicKey: ephemPublicKey, cipherText: cipherText, mac: mac};
|
var decOpts = {iv: iv, ephemPublicKey: ephemPublicKey, ciphertext: ciphertext, mac: mac};
|
||||||
|
|
||||||
it("should encrypt", function() {
|
it("should encrypt", function() {
|
||||||
return eccrypto.encrypt(publicKeyB, Buffer("test"), encOpts)
|
return eccrypto.encrypt(publicKeyB, Buffer("test"), encOpts)
|
||||||
.then(function(res) {
|
.then(function(enc) {
|
||||||
expect(bufferEqual(res.iv, iv)).to.be.true;
|
expect(bufferEqual(enc.iv, iv)).to.be.true;
|
||||||
expect(bufferEqual(res.ephemPublicKey, ephemPublicKey)).to.be.true;
|
expect(bufferEqual(enc.ephemPublicKey, ephemPublicKey)).to.be.true;
|
||||||
expect(bufferEqual(res.cipherText, cipherText)).to.be.true;
|
expect(bufferEqual(enc.ciphertext, ciphertext)).to.be.true;
|
||||||
expect(bufferEqual(res.mac, mac)).to.be.true;
|
expect(bufferEqual(enc.mac, mac)).to.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -139,56 +137,59 @@ describe("ECIES", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should encrypt and decrypt", function() {
|
it("should encrypt and decrypt", function() {
|
||||||
return eccrypto.encrypt(publicKeyA, Buffer("b to a")).then(function(res) {
|
return eccrypto.encrypt(publicKeyA, Buffer("to a")).then(function(enc) {
|
||||||
return eccrypto.decrypt(privateKeyA, res);
|
return eccrypto.decrypt(privateKeyA, enc);
|
||||||
}).then(function(msg) {
|
}).then(function(msg) {
|
||||||
expect(msg.toString()).to.equal("b to a");
|
expect(msg.toString()).to.equal("to a");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should reject promise on bad private key when decrypting", function(done) {
|
it("should reject promise on bad private key when decrypting", function(done) {
|
||||||
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(res) {
|
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(enc) {
|
||||||
eccrypto.decrypt(privateKeyB, res).catch(function() {
|
eccrypto.decrypt(privateKeyB, enc).catch(function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should reject promise on bad IV when decrypting", function(done) {
|
it("should reject promise on bad IV when decrypting", function(done) {
|
||||||
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(res) {
|
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(enc) {
|
||||||
res.iv[0] ^= 1;
|
enc.iv[0] ^= 1;
|
||||||
eccrypto.decrypt(privateKeyA, res).catch(function() {
|
eccrypto.decrypt(privateKeyA, enc).catch(function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should reject promise on bad R when decrypting", function(done) {
|
it("should reject promise on bad R when decrypting", function(done) {
|
||||||
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(res) {
|
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(enc) {
|
||||||
res.ephemPublicKey[0] ^= 1;
|
enc.ephemPublicKey[0] ^= 1;
|
||||||
eccrypto.decrypt(privateKeyA, res).catch(function() {
|
eccrypto.decrypt(privateKeyA, enc).catch(function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should reject promise on bad cipher text when decrypting", function(done) {
|
it("should reject promise on bad ciphertext when decrypting", function(done) {
|
||||||
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(res) {
|
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(enc) {
|
||||||
res.cipherText[0] ^= 1;
|
enc.ciphertext[0] ^= 1;
|
||||||
eccrypto.decrypt(privateKeyA, res).catch(function() {
|
eccrypto.decrypt(privateKeyA, enc).catch(function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should reject promise on bad MAC when decrypting", function(done) {
|
it("should reject promise on bad MAC when decrypting", function(done) {
|
||||||
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(res) {
|
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(enc) {
|
||||||
res.mac[0] ^= 1;
|
var origMac = enc.mac;
|
||||||
eccrypto.decrypt(privateKeyA, res).catch(function() {
|
enc.mac = mac.slice(1);
|
||||||
|
eccrypto.decrypt(privateKeyA, enc).catch(function() {
|
||||||
|
enc.mac = origMac;
|
||||||
|
enc.mac[10] ^= 1;
|
||||||
|
eccrypto.decrypt(privateKeyA, enc).catch(function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user