ECDH (Node)

This commit is contained in:
Kagami Hiiragi 2015-01-13 22:39:37 +03:00
parent cd217b2d02
commit 4e1001a842
8 changed files with 205 additions and 12 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/node_modules/
/npm-debug.log
/build/

View File

@ -1,3 +1,4 @@
.travis.yml
.jshint*
karma.conf.js
/.travis.yml
/.jshint*
/karma.conf.js
/build/

View File

@ -4,12 +4,7 @@ JavaScript Elliptic curve cryptography library for both browserify and node.
## Motivation
There is currently no any isomorphic ECC library which provides ECDSA, ECDH and ECIES for both Node.js and Browser and uses the fastest libraries available (e.g. [secp256k1-node](https://github.com/wanderer/secp256k1-node) is much faster than other libraries but can be used only on Node.js). So `eccrypto` is an attempt to create one. Current goals:
- [x] ~~Convert private key to public~~
- [x] ~~ECDSA~~
- [ ] ECDH
- [ ] ECIES
There is currently no any isomorphic ECC library which provides ECDSA, ECDH and ECIES for both Node.js and Browser and uses the fastest implementation available (e.g. [secp256k1-node](https://github.com/wanderer/secp256k1-node) is much faster than other libraries but can be used only on Node.js). So `eccrypto` is an attempt to create one.
## Implementation details
@ -61,6 +56,19 @@ eccrypto.sign(privateKey, msg).then(function(sig) {
### ECDH
```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);
eccrypto.derive(privateKeyA, publicKeyB).then(function(sharedKey1) {
eccrypto.derive(privateKeyB, publicKeyA).then(function(sharedKey2) {
console.log("Both shared keys are equal:", sharedKey1, sharedKey2);
});
});
```
### ECIES

61
binding.gyp Normal file
View File

@ -0,0 +1,61 @@
{
"targets": [
{
"target_name": "ecdh",
"include_dirs": ["<!(node -e \"require('nan')\")"],
"cflags": ["-Wall", "-O2"],
"sources": ["ecdh.cc"],
"conditions": [
["OS=='win'", {
"conditions": [
[
"target_arch=='x64'", {
"variables": {
"openssl_root%": "C:/OpenSSL-Win64"
},
}, {
"variables": {
"openssl_root%": "C:/OpenSSL-Win32"
}
}
]
],
"libraries": [
"-l<(openssl_root)/lib/libeay32.lib",
],
"include_dirs": [
"<(openssl_root)/include",
],
}, {
"conditions": [
[
"target_arch=='ia32'", {
"variables": {
"openssl_config_path": "<(nodedir)/deps/openssl/config/piii"
}
}
],
[
"target_arch=='x64'", {
"variables": {
"openssl_config_path": "<(nodedir)/deps/openssl/config/k8"
},
}
],
[
"target_arch=='arm'", {
"variables": {
"openssl_config_path": "<(nodedir)/deps/openssl/config/arm"
}
}
],
],
"include_dirs": [
"<(nodedir)/deps/openssl/openssl/include",
"<(openssl_config_path)"
]
}
]]
}
]
}

102
ecdh.cc Normal file
View File

@ -0,0 +1,102 @@
#include <node.h>
#include <nan.h>
#include <openssl/evp.h>
#include <openssl/ec.h>
#define PRIVKEY_SIZE 32
#define PUBKEY_SIZE 65
#define CHECK(cond) do { if (!(cond)) goto error; } while (0)
using node::Buffer;
using v8::Handle;
using v8::FunctionTemplate;
using v8::Object;
using v8::String;
int derive(const uint8_t* privkey_a, const uint8_t* pubkey_b, uint8_t* shared) {
int rc = -1;
int res;
BIGNUM* pkey_bn = NULL;
BIGNUM* peerkey_bn_x = NULL;
BIGNUM* peerkey_bn_y = NULL;
EC_KEY* pkey = NULL;
EC_KEY* peerkey = NULL;
EVP_PKEY* evp_pkey = NULL;
EVP_PKEY* evp_peerkey = NULL;
EVP_PKEY_CTX* ctx = NULL;
size_t shared_len = PRIVKEY_SIZE;
// Private key A.
CHECK((pkey_bn = BN_bin2bn(privkey_a, PRIVKEY_SIZE, NULL)) != NULL);
CHECK((pkey = EC_KEY_new_by_curve_name(NID_secp256k1)) != NULL);
CHECK(EC_KEY_set_private_key(pkey, pkey_bn) == 1);
CHECK((evp_pkey = EVP_PKEY_new()) != NULL);
CHECK(EVP_PKEY_set1_EC_KEY(evp_pkey, pkey) == 1);
// Public key B.
CHECK((peerkey_bn_x = BN_bin2bn(pubkey_b+1, PRIVKEY_SIZE, NULL)) != NULL);
CHECK((peerkey_bn_y = BN_bin2bn(pubkey_b+33, PRIVKEY_SIZE, NULL)) != NULL);
CHECK((peerkey = EC_KEY_new_by_curve_name(NID_secp256k1)) != NULL);
res = EC_KEY_set_public_key_affine_coordinates(peerkey,
peerkey_bn_x,
peerkey_bn_y);
CHECK(res == 1);
CHECK((evp_peerkey = EVP_PKEY_new()) != NULL);
CHECK(EVP_PKEY_set1_EC_KEY(evp_peerkey, peerkey) == 1);
// Derive shared secret.
CHECK((ctx = EVP_PKEY_CTX_new(evp_pkey, NULL)) != NULL);
CHECK(EVP_PKEY_derive_init(ctx) == 1);
CHECK(EVP_PKEY_derive_set_peer(ctx, evp_peerkey) == 1);
CHECK((EVP_PKEY_derive(ctx, shared, &shared_len)) == 1);
CHECK(shared_len == PRIVKEY_SIZE);
rc = 0;
error:
EVP_PKEY_CTX_free(ctx);
EVP_PKEY_free(evp_peerkey);
EC_KEY_free(peerkey);
BN_free(peerkey_bn_y);
BN_free(peerkey_bn_x);
EVP_PKEY_free(evp_pkey);
EC_KEY_free(pkey);
BN_free(pkey_bn);
return rc;
}
NAN_METHOD(Derive) {
NanScope();
if (args.Length() != 2 ||
!args[0]->IsObject() || // privkey_a
!args[1]->IsObject()) { // pubkey_b
return NanThrowError("Bad input");
}
char* privkey_a = Buffer::Data(args[0]->ToObject());
size_t privkey_a_len = Buffer::Length(args[0]->ToObject());
char* pubkey_b = Buffer::Data(args[1]->ToObject());
size_t pubkey_b_len = Buffer::Length(args[1]->ToObject());
if (privkey_a == NULL ||
privkey_a_len != PRIVKEY_SIZE ||
pubkey_b == NULL ||
pubkey_b_len != PUBKEY_SIZE ||
pubkey_b[0] != 4) {
return NanThrowError("Bad input");
}
uint8_t* shared = (uint8_t *)malloc(PRIVKEY_SIZE);
if (derive((uint8_t *)privkey_a, (uint8_t *)pubkey_b, shared)) {
free(shared);
return NanThrowError("Internal error");
}
NanReturnValue(NanBufferUse((char *)shared, PRIVKEY_SIZE));
}
void InitAll(Handle<Object> exports) {
exports->Set(
NanNew<String>("derive"),
NanNew<FunctionTemplate>(Derive)->GetFunction());
}
NODE_MODULE(ecdh, InitAll)

View File

@ -8,7 +8,10 @@
var promise = typeof Promise === "undefined" ?
require("es6-promise").Promise :
Promise;
// 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");
/**
* Compute the public key for a given private key.
@ -44,3 +47,16 @@ exports.verify = function(publicKey, msg, sig) {
return secp256k1.verify(publicKey, msg, sig) === 1 ? resolve() : reject();
});
};
/**
* Derive shared secret for given private and public keys.
* @param {Buffer} privateKeyA - Sender's private key
* @param {Buffer} publicKeyB - Recipient's public key
* @return {Promise.<Buffer>} A promise that resolves with the derived
* shared secret (Px) and rejects on bad key.
*/
exports.derive = function(privateKeyA, publicKeyB) {
return new promise(function(resolve) {
resolve(ecdh.derive(privateKeyA, publicKeyB));
});
};

View File

@ -5,6 +5,7 @@
"main": "index.js",
"browser": "browser.js",
"scripts": {
"install": "node-gyp rebuild || exit 0",
"test": "mocha && xvfb-run -a karma start && jshint .",
"m": "mocha",
"k": "xvfb-run -a karma start",
@ -49,6 +50,9 @@
"dependencies": {
"elliptic": "^1.0.1",
"es6-promise": "^2.0.1",
"nan": "^1.4.1"
},
"optionalDependencies": {
"secp256k1": "~0.0.13"
}
}

View File

@ -81,8 +81,6 @@ 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) {
@ -92,7 +90,7 @@ describe("ECDH", function() {
return eccrypto.derive(privateKeyB, publicKeyA).then(function(Px2) {
expect(Buffer.isBuffer(Px2)).to.be.true;
expect(Px2.length).to.equal(32);
expect(Px.toString("hex")).to.equal(Px2.toString("hex"));
expect(bufferEqual(Px, Px2)).to.be.true;
});
});
});
@ -110,6 +108,8 @@ describe("ECDH", function() {
});
});
if (typeof window !== "undefined") {
describe("ECIES", function() {
var ephemPrivateKey = Buffer(32);
ephemPrivateKey.fill(4);