ECDH (Node)
This commit is contained in:
parent
cd217b2d02
commit
4e1001a842
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
/node_modules/
|
/node_modules/
|
||||||
/npm-debug.log
|
/npm-debug.log
|
||||||
|
/build/
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
.travis.yml
|
/.travis.yml
|
||||||
.jshint*
|
/.jshint*
|
||||||
karma.conf.js
|
/karma.conf.js
|
||||||
|
/build/
|
||||||
|
|
20
README.md
20
README.md
|
@ -4,12 +4,7 @@ JavaScript Elliptic curve cryptography library for both browserify and node.
|
||||||
|
|
||||||
## Motivation
|
## 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:
|
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.
|
||||||
|
|
||||||
- [x] ~~Convert private key to public~~
|
|
||||||
- [x] ~~ECDSA~~
|
|
||||||
- [ ] ECDH
|
|
||||||
- [ ] ECIES
|
|
||||||
|
|
||||||
## Implementation details
|
## Implementation details
|
||||||
|
|
||||||
|
@ -61,6 +56,19 @@ eccrypto.sign(privateKey, msg).then(function(sig) {
|
||||||
### ECDH
|
### ECDH
|
||||||
|
|
||||||
```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);
|
||||||
|
|
||||||
|
eccrypto.derive(privateKeyA, publicKeyB).then(function(sharedKey1) {
|
||||||
|
eccrypto.derive(privateKeyB, publicKeyA).then(function(sharedKey2) {
|
||||||
|
console.log("Both shared keys are equal:", sharedKey1, sharedKey2);
|
||||||
|
});
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### ECIES
|
### ECIES
|
||||||
|
|
61
binding.gyp
Normal file
61
binding.gyp
Normal 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
102
ecdh.cc
Normal 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)
|
16
index.js
16
index.js
|
@ -8,7 +8,10 @@
|
||||||
var promise = typeof Promise === "undefined" ?
|
var promise = typeof Promise === "undefined" ?
|
||||||
require("es6-promise").Promise :
|
require("es6-promise").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 secp256k1 = require("secp256k1");
|
||||||
|
var ecdh = require("./build/Release/ecdh");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the public key for a given private key.
|
* 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();
|
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));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"browser": "browser.js",
|
"browser": "browser.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"install": "node-gyp rebuild || exit 0",
|
||||||
"test": "mocha && xvfb-run -a karma start && jshint .",
|
"test": "mocha && xvfb-run -a karma start && jshint .",
|
||||||
"m": "mocha",
|
"m": "mocha",
|
||||||
"k": "xvfb-run -a karma start",
|
"k": "xvfb-run -a karma start",
|
||||||
|
@ -49,6 +50,9 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"elliptic": "^1.0.1",
|
"elliptic": "^1.0.1",
|
||||||
"es6-promise": "^2.0.1",
|
"es6-promise": "^2.0.1",
|
||||||
|
"nan": "^1.4.1"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
"secp256k1": "~0.0.13"
|
"secp256k1": "~0.0.13"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
6
test.js
6
test.js
|
@ -81,8 +81,6 @@ describe("ECDSA", function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -92,7 +90,7 @@ describe("ECDH", function() {
|
||||||
return eccrypto.derive(privateKeyB, publicKeyA).then(function(Px2) {
|
return eccrypto.derive(privateKeyB, publicKeyA).then(function(Px2) {
|
||||||
expect(Buffer.isBuffer(Px2)).to.be.true;
|
expect(Buffer.isBuffer(Px2)).to.be.true;
|
||||||
expect(Px2.length).to.equal(32);
|
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() {
|
describe("ECIES", function() {
|
||||||
var ephemPrivateKey = Buffer(32);
|
var ephemPrivateKey = Buffer(32);
|
||||||
ephemPrivateKey.fill(4);
|
ephemPrivateKey.fill(4);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user