From: Ben Noordhuis Date: Thu, 21 Nov 2013 10:29:07 +0000 (+0100) Subject: crypto: support custom pbkdf2 digest methods X-Git-Tag: v0.11.11~30 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=74d9aa49d57d2c505d1d3712414ca245a805173d;p=platform%2Fupstream%2Fnodejs.git crypto: support custom pbkdf2 digest methods Make the HMAC digest method configurable. Update crypto.pbkdf2() and crypto.pbkdf2Sync() to take an extra, optional digest argument. Before this commit, SHA-1 (admittedly the most common method) was used exclusively. Fixes #6553. --- diff --git a/doc/api/crypto.markdown b/doc/api/crypto.markdown index 35d4c39..82374df 100644 --- a/doc/api/crypto.markdown +++ b/doc/api/crypto.markdown @@ -487,13 +487,25 @@ Example (obtaining a shared secret): /* alice_secret and bob_secret should be the same */ console.log(alice_secret == bob_secret); -## crypto.pbkdf2(password, salt, iterations, keylen, callback) +## crypto.pbkdf2(password, salt, iterations, keylen, [digest], 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)`. +Asynchronous PBKDF2 function. Applies the selected HMAC digest function +(default: SHA1) to derive a key of the requested length from the password, +salt and number of iterations. The callback gets two arguments: +`(err, derivedKey)`. -## crypto.pbkdf2Sync(password, salt, iterations, keylen) +Example: + + crypto.pbkdf2('secret', 'salt', 4096, 512, 'sha256', function(err, key) { + if (err) + throw err; + console.log(key.toString('hex')); // 'c5e478d...1469e50' + }); + +You can get a list of supported digest functions with +[crypto.getHashes()](#crypto_crypto_gethashes). + +## crypto.pbkdf2Sync(password, salt, iterations, keylen, [digest]) Synchronous PBKDF2 function. Returns derivedKey or throws error. diff --git a/lib/crypto.js b/lib/crypto.js index 050d54a..8226241 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -575,36 +575,47 @@ DiffieHellman.prototype.setPrivateKey = function(key, encoding) { -exports.pbkdf2 = function(password, salt, iterations, keylen, callback) { +exports.pbkdf2 = function(password, + salt, + iterations, + keylen, + digest, + callback) { + if (util.isFunction(digest)) { + callback = digest; + digest = undefined; + } + if (!util.isFunction(callback)) throw new Error('No callback provided to pbkdf2'); - return pbkdf2(password, salt, iterations, keylen, callback); + return pbkdf2(password, salt, iterations, keylen, digest, callback); }; -exports.pbkdf2Sync = function(password, salt, iterations, keylen) { - return pbkdf2(password, salt, iterations, keylen); +exports.pbkdf2Sync = function(password, salt, iterations, keylen, digest) { + return pbkdf2(password, salt, iterations, keylen, digest); }; -function pbkdf2(password, salt, iterations, keylen, callback) { +function pbkdf2(password, salt, iterations, keylen, digest, callback) { password = toBuf(password); salt = toBuf(salt); if (exports.DEFAULT_ENCODING === 'buffer') - return binding.PBKDF2(password, salt, iterations, keylen, callback); + return binding.PBKDF2(password, salt, iterations, keylen, digest, callback); // at this point, we need to handle encodings. var encoding = exports.DEFAULT_ENCODING; if (callback) { - binding.PBKDF2(password, salt, iterations, keylen, function(er, ret) { + function next(er, ret) { if (ret) ret = ret.toString(encoding); callback(er, ret); - }); + } + binding.PBKDF2(password, salt, iterations, keylen, digest, next); } else { - var ret = binding.PBKDF2(password, salt, iterations, keylen); + var ret = binding.PBKDF2(password, salt, iterations, keylen, digest); return ret.toString(encoding); } } diff --git a/src/node_crypto.cc b/src/node_crypto.cc index cf1b637..20f4137 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -3468,6 +3468,7 @@ class PBKDF2Request : public AsyncWrap { public: PBKDF2Request(Environment* env, Local object, + const EVP_MD* digest, ssize_t passlen, char* pass, ssize_t saltlen, @@ -3475,6 +3476,7 @@ class PBKDF2Request : public AsyncWrap { ssize_t iter, ssize_t keylen) : AsyncWrap(env, object), + digest_(digest), error_(0), passlen_(passlen), pass_(pass), @@ -3495,6 +3497,10 @@ class PBKDF2Request : public AsyncWrap { return &work_req_; } + inline const EVP_MD* digest() const { + return digest_; + } + inline ssize_t passlen() const { return passlen_; } @@ -3544,6 +3550,7 @@ class PBKDF2Request : public AsyncWrap { uv_work_t work_req_; private: + const EVP_MD* digest_; int error_; ssize_t passlen_; char* pass_; @@ -3556,12 +3563,13 @@ class PBKDF2Request : public AsyncWrap { void EIO_PBKDF2(PBKDF2Request* req) { - req->set_error(PKCS5_PBKDF2_HMAC_SHA1( + req->set_error(PKCS5_PBKDF2_HMAC( req->pass(), req->passlen(), reinterpret_cast(req->salt()), req->saltlen(), req->iter(), + req->digest(), req->keylen(), reinterpret_cast(req->key()))); memset(req->pass(), 0, req->passlen()); @@ -3606,6 +3614,7 @@ void PBKDF2(const FunctionCallbackInfo& args) { HandleScope handle_scope(args.GetIsolate()); Environment* env = Environment::GetCurrent(args.GetIsolate()); + const EVP_MD* digest = NULL; const char* type_error = NULL; char* pass = NULL; char* salt = NULL; @@ -3618,7 +3627,7 @@ void PBKDF2(const FunctionCallbackInfo& args) { PBKDF2Request* req = NULL; Local obj; - if (args.Length() != 4 && args.Length() != 5) { + if (args.Length() != 5 && args.Length() != 6) { type_error = "Bad parameter"; goto err; } @@ -3673,11 +3682,32 @@ void PBKDF2(const FunctionCallbackInfo& args) { goto err; } - obj = Object::New(); - req = new PBKDF2Request(env, obj, passlen, pass, saltlen, salt, iter, keylen); + if (args[4]->IsString()) { + String::Utf8Value digest_name(args[4]); + digest = EVP_get_digestbyname(*digest_name); + if (digest == NULL) { + type_error = "Bad digest name"; + goto err; + } + } + + if (digest == NULL) { + digest = EVP_sha1(); + } - if (args[4]->IsFunction()) { - obj->Set(env->ondone_string(), args[4]); + obj = Object::New(); + req = new PBKDF2Request(env, + obj, + digest, + passlen, + pass, + saltlen, + salt, + iter, + keylen); + + if (args[5]->IsFunction()) { + obj->Set(env->ondone_string(), args[5]); // XXX(trevnorris): This will need to go with the rest of domains. if (env->in_domain()) obj->Set(env->domain_string(), env->domain_array()->Get(0)); diff --git a/test/simple/test-crypto.js b/test/simple/test-crypto.js index 47a8f73..7afb958 100644 --- a/test/simple/test-crypto.js +++ b/test/simple/test-crypto.js @@ -912,6 +912,19 @@ testPBKDF2('pass\0word', 'sa\0lt', 4096, 16, '\x56\xfa\x6a\xa7\x55\x48\x09\x9d\xcc\x37\xd7\xf0\x34' + '\x25\xe0\xc3'); +(function() { + var expected = + '64c486c55d30d4c5a079b8823b7d7cb37ff0556f537da8410233bcec330ed956'; + var key = crypto.pbkdf2Sync('password', 'salt', 32, 32, 'sha256'); + assert.equal(key.toString('hex'), expected); + + crypto.pbkdf2('password', 'salt', 32, 32, 'sha256', common.mustCall(ondone)); + function ondone(err, key) { + if (err) throw err; + assert.equal(key.toString('hex'), expected); + } +})(); + function assertSorted(list) { assert.deepEqual(list, list.sort()); }