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.
This commit is contained in:
Ben Noordhuis 2013-11-21 11:29:07 +01:00 committed by Fedor Indutny
parent e6016dae34
commit 74d9aa49d5
4 changed files with 86 additions and 20 deletions

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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 (args[4]->IsFunction()) {
obj->Set(env->ondone_string(), args[4]);
if (digest == NULL) {
digest = EVP_sha1();
}
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));

View File

@ -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());
}