ECIES (Browser)
This commit is contained in:
parent
4e3f857332
commit
cd217b2d02
118
browser.js
118
browser.js
|
@ -13,6 +13,8 @@
|
||||||
var EC = require("elliptic").ec;
|
var EC = require("elliptic").ec;
|
||||||
|
|
||||||
var ec = new EC("secp256k1");
|
var ec = new EC("secp256k1");
|
||||||
|
// TODO(Kagami): Try to support IE11.
|
||||||
|
var subtle = window.crypto.subtle || window.crypto.webkitSubtle;
|
||||||
|
|
||||||
function assert(condition, message) {
|
function assert(condition, message) {
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
|
@ -20,9 +22,54 @@ function assert(condition, message) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getPublic = function(privateKey) {
|
function randomBytes(size) {
|
||||||
|
var arr = new Uint8Array(size);
|
||||||
|
window.crypto.getRandomValues(arr);
|
||||||
|
return new Buffer(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sha512(msg) {
|
||||||
|
return subtle.digest({name: "SHA-512"}, msg).then(function(hash) {
|
||||||
|
return new Buffer(new Uint8Array(hash));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAes(op) {
|
||||||
|
return function(iv, key, msg) {
|
||||||
|
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));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var aesCbcEncrypt = getAes("encrypt");
|
||||||
|
var aesCbcDecrypt = getAes("decrypt");
|
||||||
|
|
||||||
|
function hmacSha256Sign(key, msg) {
|
||||||
|
var algorithm = {name: "HMAC", hash: {name: "SHA-256"}};
|
||||||
|
var keyp = subtle.importKey("raw", key, algorithm, false, ["sign"]);
|
||||||
|
return keyp.then(function(cryptoKey) {
|
||||||
|
return subtle.sign(algorithm, cryptoKey, msg);
|
||||||
|
}).then(function(sig) {
|
||||||
|
return new Buffer(new Uint8Array(sig));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hmacSha256Verify(key, msg, sig) {
|
||||||
|
var algorithm = {name: "HMAC", hash: {name: "SHA-256"}};
|
||||||
|
var keyp = subtle.importKey("raw", key, algorithm, false, ["verify"]);
|
||||||
|
return keyp.then(function(cryptoKey) {
|
||||||
|
return subtle.verify(algorithm, cryptoKey, sig, msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var getPublic = exports.getPublic = function(privateKey) {
|
||||||
// This function has sync API so we throw an error immediately.
|
// This function has sync API so we throw an error immediately.
|
||||||
// (`elliptic` doesn't do this).
|
|
||||||
assert(privateKey.length === 32, "Bad private key");
|
assert(privateKey.length === 32, "Bad private key");
|
||||||
// XXX(Kagami): `elliptic.utils.encode` returns array for every
|
// XXX(Kagami): `elliptic.utils.encode` returns array for every
|
||||||
// encoding except `hex`.
|
// encoding except `hex`.
|
||||||
|
@ -31,23 +78,82 @@ exports.getPublic = function(privateKey) {
|
||||||
|
|
||||||
exports.sign = function(privateKey, msg) {
|
exports.sign = function(privateKey, msg) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function(resolve) {
|
||||||
|
assert(privateKey.length === 32, "Bad private key");
|
||||||
var key = ec.keyPair(privateKey);
|
var key = ec.keyPair(privateKey);
|
||||||
resolve(new Buffer(key.sign(msg).toDER()));
|
resolve(new Buffer(key.sign(msg).toDER()));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.verify = function(key, msg, sig) {
|
exports.verify = function(publicKey, msg, sig) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
key = ec.keyPair(key);
|
assert(publicKey.length === 65, "Bad public key");
|
||||||
|
assert(publicKey[0] === 4, "Bad public key");
|
||||||
|
var key = ec.keyPair(publicKey);
|
||||||
return key.verify(msg, sig) ? resolve() : reject();
|
return key.verify(msg, sig) ? resolve() : reject();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.derive = function(privateKeyA, publicKeyB) {
|
var derive = exports.derive = function(privateKeyA, publicKeyB) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function(resolve) {
|
||||||
|
assert(privateKeyA.length === 32, "Bad private key");
|
||||||
|
assert(publicKeyB.length === 65, "Bad public key");
|
||||||
|
assert(publicKeyB[0] === 4, "Bad public key");
|
||||||
var keyA = ec.keyPair(privateKeyA);
|
var keyA = ec.keyPair(privateKeyA);
|
||||||
var keyB = ec.keyPair(publicKeyB);
|
var keyB = ec.keyPair(publicKeyB);
|
||||||
var Px = keyA.derive(keyB.getPublic()); // BN instance
|
var Px = keyA.derive(keyB.getPublic()); // BN instance
|
||||||
resolve(new Buffer(Px.toString(16), "hex"));
|
resolve(new Buffer(Px.toString(16, 2), "hex"));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
var ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32);
|
||||||
|
ephemPublicKey = getPublic(ephemPrivateKey);
|
||||||
|
resolve(derive(ephemPrivateKey, publicKeyTo));
|
||||||
|
}).then(function(Px) {
|
||||||
|
return sha512(Px);
|
||||||
|
}).then(function(hash) {
|
||||||
|
iv = opts.iv || randomBytes(16);
|
||||||
|
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]);
|
||||||
|
return hmacSha256Sign(macKey, dataToMac);
|
||||||
|
}).then(function(mac) {
|
||||||
|
return {
|
||||||
|
iv: iv,
|
||||||
|
ephemPublicKey: ephemPublicKey,
|
||||||
|
cipherText: cipherText,
|
||||||
|
mac: mac,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.decrypt = function(privateKey, opts) {
|
||||||
|
assert(subtle, "WebCryptoAPI is not supported");
|
||||||
|
// Tmp variables 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(goodMac) {
|
||||||
|
assert(goodMac, "Bad MAC");
|
||||||
|
return aesCbcDecrypt(opts.iv, encryptionKey, opts.cipherText);
|
||||||
|
}).then(function(msg) {
|
||||||
|
return new Buffer(new Uint8Array(msg));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
2
index.js
2
index.js
|
@ -16,7 +16,7 @@ var secp256k1 = require("secp256k1");
|
||||||
* @return {Buffer} A 65-byte public key.
|
* @return {Buffer} A 65-byte public key.
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
var getPublic = exports.getPublic = secp256k1.createPublicKey;
|
exports.getPublic = secp256k1.createPublicKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an ECDSA signature.
|
* Create an ECDSA signature.
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/bitchan/eccrypto",
|
"homepage": "https://github.com/bitchan/eccrypto",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"buffer-equal": "~0.0.1",
|
||||||
"chai": "*",
|
"chai": "*",
|
||||||
"jshint": "*",
|
"jshint": "*",
|
||||||
"karma": "^0.12.28",
|
"karma": "^0.12.28",
|
||||||
|
@ -46,7 +47,7 @@
|
||||||
"mocha": "*"
|
"mocha": "*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"elliptic": "^1.0.0",
|
"elliptic": "^1.0.1",
|
||||||
"es6-promise": "^2.0.1",
|
"es6-promise": "^2.0.1",
|
||||||
"secp256k1": "~0.0.13"
|
"secp256k1": "~0.0.13"
|
||||||
}
|
}
|
||||||
|
|
109
test.js
109
test.js
|
@ -1,5 +1,6 @@
|
||||||
var expect = require("chai").expect;
|
var expect = require("chai").expect;
|
||||||
var crypto = require("crypto");
|
var crypto = require("crypto");
|
||||||
|
var bufferEqual = require("buffer-equal");
|
||||||
var eccrypto = require("./");
|
var eccrypto = require("./");
|
||||||
|
|
||||||
var msg = crypto.createHash("sha256").update("test").digest();
|
var msg = crypto.createHash("sha256").update("test").digest();
|
||||||
|
@ -48,15 +49,22 @@ describe("ECDSA", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should reject promise on invalid key when signing", function(done) {
|
it("should reject promise on invalid key when signing", function(done) {
|
||||||
eccrypto.sign(Buffer("test"), msg).catch(function() {
|
var k4 = Buffer("test");
|
||||||
done();
|
var k192 = Buffer("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "hex");
|
||||||
|
var k384 = Buffer("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "hex");
|
||||||
|
eccrypto.sign(k4, msg).catch(function() {
|
||||||
|
eccrypto.sign(k192, msg).catch(function() {
|
||||||
|
eccrypto.sign(k384, msg).catch(function() {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should reject promise on invalid key when verifying", function(done) {
|
it("should reject promise on invalid key when verifying", function(done) {
|
||||||
eccrypto.sign(privateKey, msg).then(function(sig) {
|
eccrypto.sign(privateKey, msg).then(function(sig) {
|
||||||
expect(Buffer.isBuffer(sig)).to.be.true;
|
expect(Buffer.isBuffer(sig)).to.be.true;
|
||||||
eccrypto.verify(Buffer("test"), msg, sig).catch(function() {
|
eccrypto.verify(Buffer("test"), msg, sig).catch(function(e) {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -74,6 +82,7 @@ describe("ECDSA", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
|
|
||||||
describe("ECDH", function() {
|
describe("ECDH", function() {
|
||||||
it("should derive shared secret from privkey A and pubkey B", function() {
|
it("should derive shared secret from privkey A and pubkey B", function() {
|
||||||
return eccrypto.derive(privateKeyA, publicKeyB).then(function(Px) {
|
return eccrypto.derive(privateKeyA, publicKeyB).then(function(Px) {
|
||||||
|
@ -87,5 +96,99 @@ describe("ECDH", function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should reject promise on bad keys", function(done) {
|
||||||
|
eccrypto.derive(Buffer("test"), publicKeyB).catch(function() {
|
||||||
|
eccrypto.derive(publicKeyB, publicKeyB).catch(function() {
|
||||||
|
eccrypto.derive(privateKeyA, privateKeyA).catch(function() {
|
||||||
|
eccrypto.derive(privateKeyB, Buffer("test")).catch(function() {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 mac = Buffer("dbb14a9b53dbd6b763dba24dc99520f570cdf8095a8571db4bf501b535fda1ed", "hex");
|
||||||
|
var encOpts = {ephemPrivateKey: ephemPrivateKey, iv: iv};
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should decrypt", function() {
|
||||||
|
return eccrypto.decrypt(privateKeyB, decOpts)
|
||||||
|
.then(function(msg) {
|
||||||
|
expect(msg.toString()).to.equal("test");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should encrypt and decrypt", function() {
|
||||||
|
return eccrypto.encrypt(publicKeyA, Buffer("b to a")).then(function(res) {
|
||||||
|
return eccrypto.decrypt(privateKeyA, res);
|
||||||
|
}).then(function(msg) {
|
||||||
|
expect(msg.toString()).to.equal("b 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() {
|
||||||
|
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() {
|
||||||
|
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() {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should reject promise on bad cipher text when decrypting", function(done) {
|
||||||
|
eccrypto.encrypt(publicKeyA, Buffer("test")).then(function(res) {
|
||||||
|
res.cipherText[0] ^= 1;
|
||||||
|
eccrypto.decrypt(privateKeyA, res).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() {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user