diff --git a/doc/api/crypto.markdown b/doc/api/crypto.markdown index b1833b28f8e..fe51a979848 100644 --- a/doc/api/crypto.markdown +++ b/doc/api/crypto.markdown @@ -233,3 +233,9 @@ or `'base64'`. Sets the Diffie-Hellman private key. Key encoding can be `'binary'`, `'hex'`, or `'base64'`. +### pbkdf2(password, salt, iterations, keylen, callback) + +Asynchronous PBKDF2 applies pseudorandom function HMAC-SHA1 to derive +a key of given length from the given password, salt and iterations. +The callback gets two arguments `(err, derivedKey)`. + diff --git a/lib/crypto.js b/lib/crypto.js index 83b2bb8122e..cb4ebbd5096 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -30,6 +30,7 @@ try { var Sign = binding.Sign; var Verify = binding.Verify; var DiffieHellman = binding.DiffieHellman; + var PBKDF2 = binding.PBKDF2; var crypto = true; } catch (e) { @@ -160,3 +161,5 @@ exports.createDiffieHellman = function(size_or_key, enc) { } } + +exports.pbkdf2 = PBKDF2; diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 2124348f9b6..14e53c83620 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -3752,6 +3752,123 @@ class DiffieHellman : public ObjectWrap { DH* dh; }; +struct pbkdf2_req { + int err; + char* pass; + size_t passlen; + char* salt; + size_t saltlen; + size_t iter; + char* key; + size_t keylen; + Persistent callback; +}; + +void +EIO_PBKDF2(eio_req* req) { + pbkdf2_req* request = (pbkdf2_req*)req->data; + request->err = PKCS5_PBKDF2_HMAC_SHA1( + request->pass, + request->passlen, + (unsigned char*)request->salt, + request->saltlen, + request->iter, + request->keylen, + (unsigned char*)request->key); + memset(request->pass, 0, request->passlen); + memset(request->salt, 0, request->saltlen); +} + +int +EIO_PBKDF2After(eio_req* req) { + HandleScope scope; + + ev_unref(EV_DEFAULT_UC); + + pbkdf2_req* request = (pbkdf2_req*)req->data; + Handle argv[2]; + if (request->err) { + argv[0] = Undefined(); + argv[1] = Encode(request->key, request->keylen, BINARY); + memset(request->key, 0, request->keylen); + } else { + argv[0] = Exception::Error(String::New("PBKDF2 error")); + argv[1] = Undefined(); + } + + TryCatch try_catch; // don't quite see the necessity of this + + request->callback->Call(Context::GetCurrent()->Global(), 2, argv); + + if (try_catch.HasCaught()) + FatalException(try_catch); + + delete[] request->pass; + delete[] request->salt; + delete[] request->key; + request->callback.Dispose(); + + delete request; + + return 0; +} + +Handle +PBKDF2(const Arguments& args) { + HandleScope scope; + + if (args.Length() != 5) + return ThrowException(Exception::TypeError(String::New("Bad parameter"))); + + ASSERT_IS_STRING_OR_BUFFER(args[0]); + ssize_t passlen = DecodeBytes(args[0], BINARY); + if (passlen < 0) + return ThrowException(Exception::TypeError(String::New("Bad password"))); + char* pass = new char[passlen]; + ssize_t pass_written = DecodeWrite(pass, passlen, args[0], BINARY); + assert(pass_written == passlen); + + ASSERT_IS_STRING_OR_BUFFER(args[1]); + ssize_t saltlen = DecodeBytes(args[1], BINARY); + if (saltlen < 0) + return ThrowException(Exception::TypeError(String::New("Bad salt"))); + char* salt = new char[saltlen]; + ssize_t salt_written = DecodeWrite(salt, saltlen, args[1], BINARY); + assert(salt_written == saltlen); + + if (!args[2]->IsNumber()) + return ThrowException(Exception::TypeError(String::New("Iterations not a number"))); + ssize_t iter = args[2]->Int32Value(); + if (iter < 0) + return ThrowException(Exception::TypeError(String::New("Bad iterations"))); + + if (!args[3]->IsNumber()) + return ThrowException(Exception::TypeError(String::New("Key length not a number"))); + ssize_t keylen = args[3]->Int32Value(); + if (keylen < 0) + return ThrowException(Exception::TypeError(String::New("Bad key length"))); + char* key = new char[keylen]; + + if (!args[4]->IsFunction()) + return ThrowException(Exception::TypeError(String::New("Callback not a function"))); + Local callback = Local::Cast(args[4]); + + pbkdf2_req* request = new pbkdf2_req; + request->err = 0; + request->pass = pass; + request->passlen = passlen; + request->salt = salt; + request->saltlen = saltlen; + request->iter = iter; + request->key = key; + request->keylen = keylen; + request->callback = Persistent::New(callback); + + eio_custom(EIO_PBKDF2, EIO_PRI_DEFAULT, EIO_PBKDF2After, request); + ev_ref(EV_DEFAULT_UC); + + return Undefined(); +} void InitCrypto(Handle target) { HandleScope scope; @@ -3785,6 +3902,8 @@ void InitCrypto(Handle target) { Sign::Initialize(target); Verify::Initialize(target); + NODE_SET_METHOD(target, "PBKDF2", PBKDF2); + subject_symbol = NODE_PSYMBOL("subject"); issuer_symbol = NODE_PSYMBOL("issuer"); valid_from_symbol = NODE_PSYMBOL("valid_from"); diff --git a/test/simple/test-crypto.js b/test/simple/test-crypto.js index b761b431ce6..023637073d9 100644 --- a/test/simple/test-crypto.js +++ b/test/simple/test-crypto.js @@ -388,3 +388,26 @@ assert.equal(rsaSignature, '5c50e3145c4e2497aadb0eabc83b342d0b0021ece0d4c4a064b7 rsaVerify.update(rsaPubPem); assert.equal(rsaVerify.verify(rsaPubPem, rsaSignature, 'hex'), 1); + +// Test PBKDF2 with RFC 6070 test vectors (except #4) + +crypto.pbkdf2('password', 'salt', 1, 20, function (err, result) { + assert.equal(result, '\x0c\x60\xc8\x0f\x96\x1f\x0e\x71\xf3\xa9\xb5\x24\xaf\x60\x12\x06\x2f\xe0\x37\xa6', 'pbkdf1 test vector 1'); +}); + +crypto.pbkdf2('password', 'salt', 2, 20, function (err, result) { + assert.equal(result, '\xea\x6c\x01\x4d\xc7\x2d\x6f\x8c\xcd\x1e\xd9\x2a\xce\x1d\x41\xf0\xd8\xde\x89\x57', 'pbkdf1 test vector 2'); +}); + +crypto.pbkdf2('password', 'salt', 4096, 20, function (err, result) { + assert.equal(result, '\x4b\x00\x79\x01\xb7\x65\x48\x9a\xbe\xad\x49\xd9\x26\xf7\x21\xd0\x65\xa4\x29\xc1', 'pbkdf1 test vector 3'); +}); + +crypto.pbkdf2('passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 25, function (err, result) { + assert.equal(result, '\x3d\x2e\xec\x4f\xe4\x1c\x84\x9b\x80\xc8\xd8\x36\x62\xc0\xe4\x4a\x8b\x29\x1a\x96\x4c\xf2\xf0\x70\x38', 'pbkdf1 test vector 5'); +}); + +crypto.pbkdf2('pass\0word', 'sa\0lt', 4096, 16, function (err, result) { + assert.equal(result, '\x56\xfa\x6a\xa7\x55\x48\x09\x9d\xcc\x37\xd7\xf0\x34\x25\xe0\xc3', 'pbkdf1 test vector 6'); +}); +