ECDH (Node)
This commit is contained in:
parent
cd217b2d02
commit
4e1001a842
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
/node_modules/
|
||||
/npm-debug.log
|
||||
/build/
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
.travis.yml
|
||||
.jshint*
|
||||
karma.conf.js
|
||||
/.travis.yml
|
||||
/.jshint*
|
||||
/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
|
||||
|
||||
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
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" ?
|
||||
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));
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
6
test.js
6
test.js
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue
Block a user