Skip to content

Instantly share code, notes, and snippets.

@ry
Created February 7, 2011 21:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ry/815320 to your computer and use it in GitHub Desktop.
Save ry/815320 to your computer and use it in GitHub Desktop.
From 1c1ed88320a5137c2aad0c9f01c76e7cc2093dac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A5vard=20Stranden?= <havard.stranden@gmail.com>
Date: Wed, 19 Jan 2011 02:00:38 +0100
Subject: [PATCH] Add bindings for Diffie-Hellman
---
doc/api/crypto.markdown | 56 +++++
lib/crypto.js | 13 ++
src/node_crypto.cc | 480 ++++++++++++++++++++++++++++++++++++++++++++
test/simple/test-crypto.js | 27 +++
4 files changed, 576 insertions(+), 0 deletions(-)
diff --git a/doc/api/crypto.markdown b/doc/api/crypto.markdown
index 9e83235..0c1a1bf 100644
--- a/doc/api/crypto.markdown
+++ b/doc/api/crypto.markdown
@@ -128,3 +128,59 @@ the PEM encoded public key, and `signature`, which is the previously calculates
signature for the data, in the `signature_format` which can be `'binary'`, `'hex'` or `'base64'`.
Returns true or false depending on the validity of the signature for the data and public key.
+
+### crypto.createDiffieHellman(prime_length)
+
+Creates a Diffie-Hellman key exchange object and generates a prime of the
+given bit length. The generator used is `2`.
+
+### crypto.createDiffieHellman(prime, encoding='binary')
+
+Creates a Diffie-Hellman key exchange object using the supplied prime. The
+generator used is `2`. Encoding can be `'binary'`, `'hex'`, or `'base64'`.
+
+### diffieHellman.generateKeys(encoding='binary')
+
+Generates private and public Diffie-Hellman key values, and returns the
+public key in the specified encoding. This key should be transferred to the
+other party. Encoding can be `'binary'`, `'hex'`, or `'base64'`.
+
+### diffieHellman.computeSecret(other_public_key, input_encoding='binary', output_encoding=input_encoding)
+
+Computes the shared secret using `other_public_key` as the other party's
+public key and returns the computed shared secret. Supplied key is
+interpreted using specified `input_encoding`, and secret is encoded using
+specified `output_encoding`. Encodings can be `'binary'`, `'hex'`, or
+`'base64'`. If no output encoding is given, the input encoding is used as
+output encoding.
+
+### diffieHellman.getPrime(encoding='binary')
+
+Returns the Diffie-Hellman prime in the specified encoding, which can be
+`'binary'`, `'hex'`, or `'base64'`.
+
+### diffieHellman.getGenerator(encoding='binary')
+
+Returns the Diffie-Hellman prime in the specified encoding, which can be
+`'binary'`, `'hex'`, or `'base64'`.
+
+### diffieHellman.getPublicKey(encoding='binary')
+
+Returns the Diffie-Hellman public key in the specified encoding, which can
+be `'binary'`, `'hex'`, or `'base64'`.
+
+### diffieHellman.getPrivateKey(encoding='binary')
+
+Returns the Diffie-Hellman private key in the specified encoding, which can
+be `'binary'`, `'hex'`, or `'base64'`.
+
+### diffieHellman.setPublicKey(public_key, encoding='binary')
+
+Sets the Diffie-Hellman public key. Key encoding can be `'binary'`, `'hex'`,
+or `'base64'`.
+
+### diffieHellman.setPrivateKey(public_key, encoding='binary')
+
+Sets the Diffie-Hellman private key. Key encoding can be `'binary'`,
+`'hex'`, or `'base64'`.
+
diff --git a/lib/crypto.js b/lib/crypto.js
index 2b6fc0e..c0cada9 100644
--- a/lib/crypto.js
+++ b/lib/crypto.js
@@ -8,6 +8,7 @@ try {
var Decipher = binding.Decipher;
var Sign = binding.Sign;
var Verify = binding.Verify;
+ var DiffieHellman = binding.DiffieHellman;
var crypto = true;
} catch (e) {
@@ -104,3 +105,15 @@ exports.Verify = Verify;
exports.createVerify = function(algorithm) {
return (new Verify).init(algorithm);
};
+
+exports.DiffieHellman = DiffieHellman;
+exports.createDiffieHellman = function(size_or_key, enc) {
+ if (!size_or_key) {
+ return new DiffieHellman();
+ } else if (!enc) {
+ return new DiffieHellman(size_or_key);
+ } else {
+ return new DiffieHellman(size_or_key, enc);
+ }
+
+}
diff --git a/src/node_crypto.cc b/src/node_crypto.cc
index 4abdc5d..a94e4d4 100644
--- a/src/node_crypto.cc
+++ b/src/node_crypto.cc
@@ -2710,7 +2710,486 @@ class Verify : public ObjectWrap {
};
+class DiffieHellman : public ObjectWrap {
+ public:
+ static void Initialize (v8::Handle<v8::Object> target) {
+ HandleScope scope;
+
+ Local<FunctionTemplate> t = FunctionTemplate::New(New);
+
+ t->InstanceTemplate()->SetInternalFieldCount(1);
+
+ NODE_SET_PROTOTYPE_METHOD(t, "generateKeys", GenerateKeys);
+ NODE_SET_PROTOTYPE_METHOD(t, "computeSecret", ComputeSecret);
+ NODE_SET_PROTOTYPE_METHOD(t, "getPrime", GetPrime);
+ NODE_SET_PROTOTYPE_METHOD(t, "getGenerator", GetGenerator);
+ NODE_SET_PROTOTYPE_METHOD(t, "getPublicKey", GetPublicKey);
+ NODE_SET_PROTOTYPE_METHOD(t, "getPrivateKey", GetPrivateKey);
+ NODE_SET_PROTOTYPE_METHOD(t, "setPublicKey", SetPublicKey);
+ NODE_SET_PROTOTYPE_METHOD(t, "setPrivateKey", SetPrivateKey);
+
+ target->Set(String::NewSymbol("DiffieHellman"), t->GetFunction());
+ }
+
+ bool Init(int primeLength) {
+ dh = DH_new();
+ DH_generate_parameters_ex(dh, primeLength, DH_GENERATOR_2, 0);
+ bool result = VerifyContext();
+ if (!result) return false;
+ initialised_ = true;
+ return true;
+ }
+
+ bool Init(unsigned char* p, int p_len) {
+ dh = DH_new();
+ dh->p = BN_bin2bn(p, p_len, 0);
+ dh->g = BN_new();
+ if (!BN_set_word(dh->g, 2)) return false;
+ bool result = VerifyContext();
+ if (!result) return false;
+ initialised_ = true;
+ return true;
+ }
+
+ protected:
+ static Handle<Value> New (const Arguments& args) {
+ HandleScope scope;
+
+ DiffieHellman* diffieHellman = new DiffieHellman();
+ bool initialized = false;
+
+ if (args.Length() > 0) {
+ if (args[0]->IsInt32()) {
+ diffieHellman->Init(args[0]->Int32Value());
+ initialized = true;
+ } else {
+ if (args[0]->IsString()) {
+ char* buf;
+ int len;
+ if (args.Length() > 1 && args[1]->IsString()) {
+ len = DecodeWithEncoding(args[0], args[1], &buf);
+ } else {
+ len = DecodeBinary(args[0], &buf);
+ }
+
+ if (len == -1) {
+ delete[] buf;
+ return ThrowException(Exception::Error(String::New("Invalid argument")));
+ } else {
+ diffieHellman->Init(reinterpret_cast<unsigned char*>(buf), len);
+ delete[] buf;
+ initialized = true;
+ }
+ } else if (Buffer::HasInstance(args[0])) {
+ Local<Object> buffer = args[0]->ToObject();
+ diffieHellman->Init(
+ reinterpret_cast<unsigned char*>(Buffer::Data(buffer)),
+ Buffer::Length(buffer));
+ initialized = true;
+ }
+ }
+ }
+
+ if (!initialized) {
+ return ThrowException(Exception::Error(String::New("Initialization failed")));
+ }
+
+ diffieHellman->Wrap(args.This());
+
+ return args.This();
+ }
+
+ static Handle<Value> GenerateKeys (const Arguments& args) {
+ DiffieHellman* diffieHellman = ObjectWrap::Unwrap<DiffieHellman>(args.This());
+
+ HandleScope scope;
+
+ if (!diffieHellman->initialised_) {
+ return ThrowException(Exception::Error(String::New("Not initialized")));
+ }
+
+ if (!DH_generate_key(diffieHellman->dh)) {
+ return ThrowException(Exception::Error(String::New("Key generation failed")));
+ }
+
+ Local<Value> outString;
+
+ int dataSize = BN_num_bytes(diffieHellman->dh->pub_key);
+ char* data = new char[dataSize];
+ BN_bn2bin(diffieHellman->dh->pub_key, reinterpret_cast<unsigned char*>(data));
+
+ if (args.Length() > 0 && args[0]->IsString()) {
+ outString = EncodeWithEncoding(args[0], data, dataSize);
+ } else {
+ outString = Encode(data, dataSize, BINARY);
+ }
+ delete[] data;
+
+ return scope.Close(outString);
+ }
+
+ static Handle<Value> GetPrime (const Arguments& args) {
+ DiffieHellman* diffieHellman = ObjectWrap::Unwrap<DiffieHellman>(args.This());
+
+ HandleScope scope;
+
+ if (!diffieHellman->initialised_) {
+ return ThrowException(Exception::Error(String::New("Not initialized")));
+ }
+
+ int dataSize = BN_num_bytes(diffieHellman->dh->p);
+ char* data = new char[dataSize];
+ BN_bn2bin(diffieHellman->dh->p, reinterpret_cast<unsigned char*>(data));
+
+ Local<Value> outString;
+
+ if (args.Length() > 0 && args[0]->IsString()) {
+ outString = EncodeWithEncoding(args[0], data, dataSize);
+ } else {
+ outString = Encode(data, dataSize, BINARY);
+ }
+
+ delete[] data;
+
+ return scope.Close(outString);
+ }
+
+ static Handle<Value> GetGenerator (const Arguments& args) {
+ DiffieHellman* diffieHellman = ObjectWrap::Unwrap<DiffieHellman>(args.This());
+
+ HandleScope scope;
+
+ if (!diffieHellman->initialised_) {
+ return ThrowException(Exception::Error(String::New("Not initialized")));
+ }
+
+ int dataSize = BN_num_bytes(diffieHellman->dh->g);
+ char* data = new char[dataSize];
+ BN_bn2bin(diffieHellman->dh->g, reinterpret_cast<unsigned char*>(data));
+
+ Local<Value> outString;
+
+ if (args.Length() > 0 && args[0]->IsString()) {
+ outString = EncodeWithEncoding(args[0], data, dataSize);
+ } else {
+ outString = Encode(data, dataSize, BINARY);
+ }
+
+ delete[] data;
+
+ return scope.Close(outString);
+ }
+
+ static Handle<Value> GetPublicKey (const Arguments& args) {
+ DiffieHellman* diffieHellman = ObjectWrap::Unwrap<DiffieHellman>(args.This());
+
+ HandleScope scope;
+
+ if (!diffieHellman->initialised_) {
+ return ThrowException(Exception::Error(String::New("Not initialized")));
+ }
+
+ if (diffieHellman->dh->pub_key == NULL) {
+ return ThrowException(Exception::Error(String::New("No public key - did you forget to generate one?")));
+ }
+
+ int dataSize = BN_num_bytes(diffieHellman->dh->pub_key);
+ char* data = new char[dataSize];
+ BN_bn2bin(diffieHellman->dh->pub_key, reinterpret_cast<unsigned char*>(data));
+
+ Local<Value> outString;
+
+ if (args.Length() > 0 && args[0]->IsString()) {
+ outString = EncodeWithEncoding(args[0], data, dataSize);
+ } else {
+ outString = Encode(data, dataSize, BINARY);
+ }
+
+ delete[] data;
+
+ return scope.Close(outString);
+ }
+
+ static Handle<Value> GetPrivateKey (const Arguments& args) {
+ DiffieHellman* diffieHellman = ObjectWrap::Unwrap<DiffieHellman>(args.This());
+
+ HandleScope scope;
+
+ if (!diffieHellman->initialised_) {
+ return ThrowException(Exception::Error(String::New("Not initialized")));
+ }
+
+ if (diffieHellman->dh->priv_key == NULL) {
+ return ThrowException(Exception::Error(String::New("No private key - did you forget to generate one?")));
+ }
+ int dataSize = BN_num_bytes(diffieHellman->dh->priv_key);
+ char* data = new char[dataSize];
+ BN_bn2bin(diffieHellman->dh->priv_key, reinterpret_cast<unsigned char*>(data));
+
+ Local<Value> outString;
+
+ if (args.Length() > 0 && args[0]->IsString()) {
+ outString = EncodeWithEncoding(args[0], data, dataSize);
+ } else {
+ outString = Encode(data, dataSize, BINARY);
+ }
+
+ delete[] data;
+
+ return scope.Close(outString);
+ }
+
+ static Handle<Value> ComputeSecret (const Arguments& args) {
+ HandleScope scope;
+
+ DiffieHellman* diffieHellman = ObjectWrap::Unwrap<DiffieHellman>(args.This());
+
+ if (!diffieHellman->initialised_) {
+ return ThrowException(Exception::Error(String::New("Not initialized")));
+ }
+
+ BIGNUM* key = 0;
+
+ if (args.Length() == 0) {
+ return ThrowException(Exception::Error(String::New("First argument must be other party's public key")));
+ } else {
+ if (args[0]->IsString()) {
+ char* buf;
+ int len;
+ if (args.Length() > 1) {
+ len = DecodeWithEncoding(args[0], args[1], &buf);
+ } else {
+ len = DecodeBinary(args[0], &buf);
+ }
+ if (len == -1) {
+ delete[] buf;
+ return ThrowException(Exception::Error(String::New("Invalid argument")));
+ }
+ key = BN_bin2bn(reinterpret_cast<unsigned char*>(buf), len, 0);
+ delete[] buf;
+ } else if (Buffer::HasInstance(args[0])) {
+ Local<Object> buffer = args[0]->ToObject();
+ key = BN_bin2bn(
+ reinterpret_cast<unsigned char*>(Buffer::Data(buffer)),
+ Buffer::Length(buffer), 0);
+ } else {
+ return ThrowException(Exception::Error(String::New("First argument must be other party's public key")));
+ }
+ }
+
+ int dataSize = DH_size(diffieHellman->dh);
+ char* data = new char[dataSize];
+
+ int size = DH_compute_key(reinterpret_cast<unsigned char*>(data),
+ key, diffieHellman->dh);
+ BN_free(key);
+
+ Local<Value> outString;
+
+ if (size == -1) {
+ int checkResult;
+ if (!DH_check_pub_key(diffieHellman->dh, key, &checkResult)) {
+ return ThrowException(Exception::Error(String::New("Invalid key")));
+ } else if (checkResult) {
+ if (checkResult & DH_CHECK_PUBKEY_TOO_SMALL) {
+ return ThrowException(Exception::Error(String::New("Supplied key is too small")));
+ } else if (checkResult & DH_CHECK_PUBKEY_TOO_LARGE) {
+ return ThrowException(Exception::Error(String::New("Supplied key is too large")));
+ } else {
+ return ThrowException(Exception::Error(String::New("Invalid key")));
+ }
+ } else {
+ return ThrowException(Exception::Error(String::New("Invalid key")));
+ }
+ } else {
+ if (args.Length() > 2 && args[2]->IsString()) {
+ outString = EncodeWithEncoding(args[2], data, dataSize);
+ } else if (args.Length() > 1 && args[1]->IsString()) {
+ outString = EncodeWithEncoding(args[1], data, dataSize);
+ } else {
+ outString = Encode(data, dataSize, BINARY);
+ }
+ }
+
+ delete[] data;
+ return scope.Close(outString);
+ }
+
+ static Handle<Value> SetPublicKey(const Arguments& args) {
+ HandleScope scope;
+
+ DiffieHellman* diffieHellman = ObjectWrap::Unwrap<DiffieHellman>(args.This());
+
+ if (!diffieHellman->initialised_) {
+ return ThrowException(Exception::Error(String::New("Not initialized")));
+ }
+
+ if (args.Length() == 0) {
+ return ThrowException(Exception::Error(String::New("First argument must be public key")));
+ } else {
+ if (args[0]->IsString()) {
+ char* buf;
+ int len;
+ if (args.Length() > 1) {
+ len = DecodeWithEncoding(args[0], args[1], &buf);
+ } else {
+ len = DecodeBinary(args[0], &buf);
+ }
+ if (len == -1) {
+ delete[] buf;
+ return ThrowException(Exception::Error(String::New("Invalid argument")));
+ }
+ diffieHellman->dh->pub_key =
+ BN_bin2bn(reinterpret_cast<unsigned char*>(buf), len, 0);
+ delete[] buf;
+ } else if (Buffer::HasInstance(args[0])) {
+ Local<Object> buffer = args[0]->ToObject();
+ diffieHellman->dh->pub_key =
+ BN_bin2bn(
+ reinterpret_cast<unsigned char*>(Buffer::Data(buffer)),
+ Buffer::Length(buffer), 0);
+ } else {
+ return ThrowException(Exception::Error(String::New("First argument must be public key")));
+ }
+ }
+
+ return args.This();
+ }
+
+ static Handle<Value> SetPrivateKey(const Arguments& args) {
+ HandleScope scope;
+
+ DiffieHellman* diffieHellman = ObjectWrap::Unwrap<DiffieHellman>(args.This());
+
+ if (!diffieHellman->initialised_) {
+ return ThrowException(Exception::Error(String::New("Not initialized")));
+ }
+
+ if (args.Length() == 0) {
+ return ThrowException(Exception::Error(String::New("First argument must be private key")));
+ } else {
+ if (args[0]->IsString()) {
+ char* buf;
+ int len;
+ if (args.Length() > 1) {
+ len = DecodeWithEncoding(args[0], args[1], &buf);
+ } else {
+ len = DecodeBinary(args[0], &buf);
+ }
+ if (len == -1) {
+ delete[] buf;
+ return ThrowException(Exception::Error(String::New("Invalid argument")));
+ }
+ diffieHellman->dh->priv_key =
+ BN_bin2bn(reinterpret_cast<unsigned char*>(buf), len, 0);
+ delete[] buf;
+ } else if (Buffer::HasInstance(args[0])) {
+ Local<Object> buffer = args[0]->ToObject();
+ diffieHellman->dh->priv_key =
+ BN_bin2bn(
+ reinterpret_cast<unsigned char*>(Buffer::Data(buffer)),
+ Buffer::Length(buffer), 0);
+ } else {
+ return ThrowException(Exception::Error(String::New("First argument must be private key")));
+ }
+ }
+
+ return args.This();
+ }
+
+ DiffieHellman () : ObjectWrap () {
+ initialised_ = false;
+ dh = NULL;
+ }
+
+ ~DiffieHellman () {
+ if (dh != NULL) {
+ DH_free(dh);
+ }
+ }
+
+ private:
+ bool VerifyContext() {
+ int codes;
+ if (!DH_check(dh, &codes)) return false;
+ if (codes & DH_CHECK_P_NOT_SAFE_PRIME) return false;
+ if (codes & DH_CHECK_P_NOT_PRIME) return false;
+ if (codes & DH_UNABLE_TO_CHECK_GENERATOR) return false;
+ if (codes & DH_NOT_SUITABLE_GENERATOR) return false;
+ return true;
+ }
+
+ static int DecodeBinary(Handle<Value> str, char** buf) {
+ int len = DecodeBytes(str);
+ *buf = new char[len];
+ int written = DecodeWrite(*buf, len, str, BINARY);
+ if(written != len) {
+ return -1;
+ }
+ return len;
+ }
+
+ static int DecodeWithEncoding(Handle<Value> str, Handle<Value> enc, char** buf) {
+ int len = DecodeBinary(str, buf);
+ if (len == -1) {
+ return len;
+ }
+ String::Utf8Value encoding(enc->ToString());
+ char* retbuf = 0;
+ int retlen;
+
+ if (strcasecmp(*encoding, "hex") == 0) {
+ HexDecode((unsigned char*)*buf, len, &retbuf, &retlen);
+
+ } else if (strcasecmp(*encoding, "base64") == 0) {
+ unbase64((unsigned char*)*buf, len, &retbuf, &retlen);
+
+ } else if (strcasecmp(*encoding, "binary") == 0) {
+ // Binary - do nothing
+ } else {
+ fprintf(stderr, "node-crypto : Diffie-Hellman parameter encoding "
+ "can be binary, hex or base64\n");
+ }
+
+ if (retbuf != 0) {
+ delete [] *buf;
+ *buf = retbuf;
+ len = retlen;
+ }
+
+ return len;
+ }
+
+ static Local<Value> EncodeWithEncoding(Handle<Value> enc, char* buf, int len) {
+ HandleScope scope;
+
+ Local<Value> outString;
+ String::Utf8Value encoding(enc->ToString());
+ char* retbuf;
+ int retlen;
+ if (strcasecmp(*encoding, "hex") == 0) {
+ // Hex encoding
+ HexEncode(reinterpret_cast<unsigned char*>(buf), len, &retbuf, &retlen);
+ outString = Encode(retbuf, retlen, BINARY);
+ delete [] retbuf;
+ } else if (strcasecmp(*encoding, "base64") == 0) {
+ base64(reinterpret_cast<unsigned char*>(buf), len, &retbuf, &retlen);
+ outString = Encode(retbuf, retlen, BINARY);
+ delete [] retbuf;
+ } else if (strcasecmp(*encoding, "binary") == 0) {
+ outString = Encode(buf, len, BINARY);
+ } else {
+ fprintf(stderr, "node-crypto : Diffie-Hellman parameter encoding "
+ "can be binary, hex or base64\n");
+ }
+
+ return scope.Close(outString);
+ }
+
+ bool initialised_;
+ DH* dh;
+};
@@ -2727,6 +3206,7 @@ void InitCrypto(Handle<Object> target) {
Connection::Initialize(target);
Cipher::Initialize(target);
Decipher::Initialize(target);
+ DiffieHellman::Initialize(target);
Hmac::Initialize(target);
Hash::Initialize(target);
Sign::Initialize(target);
diff --git a/test/simple/test-crypto.js b/test/simple/test-crypto.js
index 6083cac..c9e7724 100644
--- a/test/simple/test-crypto.js
+++ b/test/simple/test-crypto.js
@@ -119,3 +119,30 @@ txt += decipher.final('utf8');
assert.equal(txt, plaintext, 'encryption and decryption with key and iv');
+// Test Diffie-Hellman with two parties sharing a secret,
+// using various encodings as we go along
+var dh1 = crypto.createDiffieHellman(256);
+var p1 = dh1.getPrime('base64');
+var dh2 = crypto.createDiffieHellman(p1, 'base64');
+var key1 = dh1.generateKeys();
+var key2 = dh2.generateKeys('hex');
+var secret1 = dh1.computeSecret(key2, 'hex', 'base64');
+var secret2 = dh2.computeSecret(key1, 'binary', 'base64');
+
+assert.equal(secret1, secret2);
+
+// Create "another dh1" using generated keys from dh1,
+// and compute secret again
+var dh3 = crypto.createDiffieHellman(p1, 'base64');
+var privkey1 = dh1.getPrivateKey();
+dh3.setPublicKey(key1);
+dh3.setPrivateKey(privkey1);
+
+assert.equal(dh1.getPrime(), dh3.getPrime());
+assert.equal(dh1.getGenerator(), dh3.getGenerator());
+assert.equal(dh1.getPublicKey(), dh3.getPublicKey());
+assert.equal(dh1.getPrivateKey(), dh3.getPrivateKey());
+
+var secret3 = dh3.computeSecret(key2, 'hex', 'base64');
+
+assert.equal(secret1, secret3);
--
1.7.3.1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment