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 = new EC("secp256k1");
|
||||
// TODO(Kagami): Try to support IE11.
|
||||
var subtle = window.crypto.subtle || window.crypto.webkitSubtle;
|
||||
|
||||
function assert(condition, message) {
|
||||
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.
|
||||
// (`elliptic` doesn't do this).
|
||||
assert(privateKey.length === 32, "Bad private key");
|
||||
// XXX(Kagami): `elliptic.utils.encode` returns array for every
|
||||
// encoding except `hex`.
|
||||
|
@ -31,23 +78,82 @@ exports.getPublic = function(privateKey) {
|
|||
|
||||
exports.sign = function(privateKey, msg) {
|
||||
return new Promise(function(resolve) {
|
||||
assert(privateKey.length === 32, "Bad private key");
|
||||
var key = ec.keyPair(privateKey);
|
||||
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) {
|
||||
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();
|
||||
});
|
||||
};
|
||||
|
||||
exports.derive = function(privateKeyA, publicKeyB) {
|
||||
var derive = exports.derive = function(privateKeyA, publicKeyB) {
|
||||
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 keyB = ec.keyPair(publicKeyB);
|
||||
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.
|
||||
* @function
|
||||
*/
|
||||
var getPublic = exports.getPublic = secp256k1.createPublicKey;
|
||||
exports.getPublic = secp256k1.createPublicKey;
|
||||
|
||||
/**
|
||||
* Create an ECDSA signature.
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
},
|
||||
"homepage": "https://github.com/bitchan/eccrypto",
|
||||
"devDependencies": {
|
||||
"buffer-equal": "~0.0.1",
|
||||
"chai": "*",
|
||||
"jshint": "*",
|
||||
"karma": "^0.12.28",
|
||||
|
@ -46,7 +47,7 @@
|
|||
"mocha": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"elliptic": "^1.0.0",
|
||||
"elliptic": "^1.0.1",
|
||||
"es6-promise": "^2.0.1",
|
||||
"secp256k1": "~0.0.13"
|
||||
}
|
||||
|
|
109
test.js
109
test.js
|
@ -1,5 +1,6 @@
|
|||
var expect = require("chai").expect;
|
||||
var crypto = require("crypto");
|
||||
var bufferEqual = require("buffer-equal");
|
||||
var eccrypto = require("./");
|
||||
|
||||
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) {
|
||||
eccrypto.sign(Buffer("test"), msg).catch(function() {
|
||||
done();
|
||||
var k4 = Buffer("test");
|
||||
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) {
|
||||
eccrypto.sign(privateKey, msg).then(function(sig) {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
@ -74,6 +82,7 @@ describe("ECDSA", function() {
|
|||
});
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
|
||||
describe("ECDH", function() {
|
||||
it("should derive shared secret from privkey A and pubkey B", function() {
|
||||
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