summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Noordhuis <info@bnoordhuis.nl>2013-11-21 11:29:07 +0100
committerFedor Indutny <fedor.indutny@gmail.com>2014-01-22 15:58:07 +0400
commit74d9aa49d57d2c505d1d3712414ca245a805173d (patch)
tree70f29529c7ea4d19fe0e275b9edb2043d64f5d4a
parente6016dae348cf105be435abf4b8b318801c09900 (diff)
downloadnode-74d9aa49d57d2c505d1d3712414ca245a805173d.tar.gz
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.
-rw-r--r--doc/api/crypto.markdown22
-rw-r--r--lib/crypto.js29
-rw-r--r--src/node_crypto.cc42
-rw-r--r--test/simple/test-crypto.js13
4 files changed, 86 insertions, 20 deletions
diff --git a/doc/api/crypto.markdown b/doc/api/crypto.markdown
index 35d4c39f4..82374df22 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 050d54afd..822624149 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 cf1b6379f..20f4137ea 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> 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<unsigned char*>(req->salt()),
req->saltlen(),
req->iter(),
+ req->digest(),
req->keylen(),
reinterpret_cast<unsigned char*>(req->key())));
memset(req->pass(), 0, req->passlen());
@@ -3606,6 +3614,7 @@ void PBKDF2(const FunctionCallbackInfo<Value>& 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<Value>& args) {
PBKDF2Request* req = NULL;
Local<Object> 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<Value>& 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 47a8f73bf..7afb958a9 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());
}