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
|
||||
|
||||
```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
|
||||
|
|
28
browser.js
28
browser.js
|
@ -35,14 +35,14 @@ function sha512(msg) {
|
|||
}
|
||||
|
||||
function getAes(op) {
|
||||
return function(iv, key, msg) {
|
||||
return function(iv, key, data) {
|
||||
var importAlgorithm = {name: "AES-CBC"};
|
||||
var keyp = subtle.importKey("raw", key, importAlgorithm, false, [op]);
|
||||
return keyp.then(function(cryptoKey) {
|
||||
var encAlgorithm = {name: "AES-CBC", iv: iv};
|
||||
return subtle[op](encAlgorithm, cryptoKey, msg);
|
||||
}).then(function(cipherText) {
|
||||
return new Buffer(new Uint8Array(cipherText));
|
||||
return subtle[op](encAlgorithm, cryptoKey, data);
|
||||
}).then(function(result) {
|
||||
return new Buffer(new Uint8Array(result));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ exports.encrypt = function(publicKeyTo, msg, opts) {
|
|||
assert(subtle, "WebCryptoAPI is not supported");
|
||||
opts = opts || {};
|
||||
// Tmp variables to save context from flat promises;
|
||||
var iv, ephemPublicKey, cipherText, macKey;
|
||||
var iv, ephemPublicKey, ciphertext, macKey;
|
||||
return new Promise(function(resolve) {
|
||||
var ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32);
|
||||
ephemPublicKey = getPublic(ephemPrivateKey);
|
||||
|
@ -121,15 +121,15 @@ exports.encrypt = function(publicKeyTo, msg, opts) {
|
|||
var encryptionKey = hash.slice(0, 32);
|
||||
macKey = hash.slice(32);
|
||||
return aesCbcEncrypt(iv, encryptionKey, msg);
|
||||
}).then(function(encrypted) {
|
||||
cipherText = encrypted;
|
||||
var dataToMac = Buffer.concat([iv, ephemPublicKey, cipherText]);
|
||||
}).then(function(data) {
|
||||
ciphertext = data;
|
||||
var dataToMac = Buffer.concat([iv, ephemPublicKey, ciphertext]);
|
||||
return hmacSha256Sign(macKey, dataToMac);
|
||||
}).then(function(mac) {
|
||||
return {
|
||||
iv: iv,
|
||||
ephemPublicKey: ephemPublicKey,
|
||||
cipherText: cipherText,
|
||||
ciphertext: ciphertext,
|
||||
mac: mac,
|
||||
};
|
||||
});
|
||||
|
@ -137,7 +137,7 @@ exports.encrypt = function(publicKeyTo, msg, opts) {
|
|||
|
||||
exports.decrypt = function(privateKey, opts) {
|
||||
assert(subtle, "WebCryptoAPI is not supported");
|
||||
// Tmp variables to save context from flat promises;
|
||||
// Tmp variable to save context from flat promises;
|
||||
var encryptionKey;
|
||||
return derive(privateKey, opts.ephemPublicKey).then(function(Px) {
|
||||
return sha512(Px);
|
||||
|
@ -147,12 +147,12 @@ exports.decrypt = function(privateKey, opts) {
|
|||
var dataToMac = Buffer.concat([
|
||||
opts.iv,
|
||||
opts.ephemPublicKey,
|
||||
opts.cipherText
|
||||
opts.ciphertext
|
||||
]);
|
||||
return hmacSha256Verify(macKey, dataToMac, opts.mac);
|
||||
}).then(function(goodMac) {
|
||||
assert(goodMac, "Bad MAC");
|
||||
return aesCbcDecrypt(opts.iv, encryptionKey, opts.cipherText);
|
||||
}).then(function(macGood) {
|
||||
assert(macGood, "Bad MAC");
|
||||
return aesCbcDecrypt(opts.iv, encryptionKey, opts.ciphertext);
|
||||
}).then(function(msg) {
|
||||
return new Buffer(new Uint8Array(msg));
|
||||
});
|
||||
|
|
119
index.js
119
index.js
|
@ -8,18 +8,59 @@
|
|||
var promise = typeof Promise === "undefined" ?
|
||||
require("es6-promise").Promise :
|
||||
Promise;
|
||||
var crypto = require("crypto");
|
||||
// TODO(Kagami): We may fallback to pure JS implementation
|
||||
// (`browser.js`) if this modules are failed to load.
|
||||
var secp256k1 = require("secp256k1");
|
||||
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.
|
||||
* @param {Buffer} privateKey - A 32-byte private key
|
||||
* @return {Buffer} A 65-byte public key.
|
||||
* @function
|
||||
*/
|
||||
exports.getPublic = secp256k1.createPublicKey;
|
||||
var getPublic = exports.getPublic = secp256k1.createPublicKey;
|
||||
|
||||
/**
|
||||
* Create an ECDSA signature.
|
||||
|
@ -50,13 +91,81 @@ exports.verify = function(publicKey, msg, sig) {
|
|||
|
||||
/**
|
||||
* Derive shared secret for given private and public keys.
|
||||
* @param {Buffer} privateKeyA - Sender's private key
|
||||
* @param {Buffer} publicKeyB - Recipient's public key
|
||||
* @param {Buffer} privateKeyA - Sender's private key (32 bytes)
|
||||
* @param {Buffer} publicKeyB - Recipient's public key (65 bytes)
|
||||
* @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) {
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
|
57
test.js
57
test.js
|
@ -108,26 +108,24 @@ describe("ECDH", function() {
|
|||
});
|
||||
});
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
|
||||
describe("ECIES", function() {
|
||||
var ephemPrivateKey = Buffer(32);
|
||||
ephemPrivateKey.fill(4);
|
||||
var ephemPublicKey = eccrypto.getPublic(ephemPrivateKey);
|
||||
var iv = Buffer(16);
|
||||
iv.fill(5);
|
||||
var cipherText = Buffer("bbf3f0e7486b552b0e2ba9c4ca8c4579", "hex");
|
||||
var ciphertext = Buffer("bbf3f0e7486b552b0e2ba9c4ca8c4579", "hex");
|
||||
var mac = Buffer("dbb14a9b53dbd6b763dba24dc99520f570cdf8095a8571db4bf501b535fda1ed", "hex");
|
||||
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() {
|
||||
return eccrypto.encrypt(publicKeyB, Buffer("test"), encOpts)
|
||||
.then(function(res) {
|
||||
expect(bufferEqual(res.iv, iv)).to.be.true;
|
||||
expect(bufferEqual(res.ephemPublicKey, ephemPublicKey)).to.be.true;
|
||||
expect(bufferEqual(res.cipherText, cipherText)).to.be.true;
|
||||
expect(bufferEqual(res.mac, mac)).to.be.true;
|
||||
.then(function(enc) {
|
||||
expect(bufferEqual(enc.iv, iv)).to.be.true;
|
||||
expect(bufferEqual(enc.ephemPublicKey, ephemPublicKey)).to.be.true;
|
||||
expect(bufferEqual(enc.ciphertext, ciphertext)).to.be.true;
|
||||
expect(bufferEqual(enc.mac, mac)).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -139,56 +137,59 @@ describe("ECIES", function() {
|
|||
});
|
||||
|
||||
it("should encrypt and decrypt", function() {
|
||||
return eccrypto.encrypt(publicKeyA, Buffer("b to a")).then(function(res) {
|
||||
return eccrypto.decrypt(privateKeyA, res);
|
||||
return eccrypto.encrypt(publicKeyA, Buffer("to a")).then(function(enc) {
|
||||
return eccrypto.decrypt(privateKeyA, enc);
|
||||
}).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) {
|
||||
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(res) {
|
||||
eccrypto.decrypt(privateKeyB, res).catch(function() {
|
||||
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(enc) {
|
||||
eccrypto.decrypt(privateKeyB, enc).catch(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should reject promise on bad IV when decrypting", function(done) {
|
||||
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(res) {
|
||||
res.iv[0] ^= 1;
|
||||
eccrypto.decrypt(privateKeyA, res).catch(function() {
|
||||
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(enc) {
|
||||
enc.iv[0] ^= 1;
|
||||
eccrypto.decrypt(privateKeyA, enc).catch(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should reject promise on bad R when decrypting", function(done) {
|
||||
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(res) {
|
||||
res.ephemPublicKey[0] ^= 1;
|
||||
eccrypto.decrypt(privateKeyA, res).catch(function() {
|
||||
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(enc) {
|
||||
enc.ephemPublicKey[0] ^= 1;
|
||||
eccrypto.decrypt(privateKeyA, enc).catch(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should reject promise on bad ciphertext when decrypting", function(done) {
|
||||
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(res) {
|
||||
res.cipherText[0] ^= 1;
|
||||
eccrypto.decrypt(privateKeyA, res).catch(function() {
|
||||
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(enc) {
|
||||
enc.ciphertext[0] ^= 1;
|
||||
eccrypto.decrypt(privateKeyA, enc).catch(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should reject promise on bad MAC when decrypting", function(done) {
|
||||
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(res) {
|
||||
res.mac[0] ^= 1;
|
||||
eccrypto.decrypt(privateKeyA, res).catch(function() {
|
||||
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(enc) {
|
||||
var origMac = enc.mac;
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user