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:
parent
e6016dae34
commit
74d9aa49d5
@ -487,13 +487,25 @@ Example (obtaining a shared secret):
|
|||||||
/* alice_secret and bob_secret should be the same */
|
/* alice_secret and bob_secret should be the same */
|
||||||
console.log(alice_secret == bob_secret);
|
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
|
Asynchronous PBKDF2 function. Applies the selected HMAC digest function
|
||||||
a key of given length from the given password, salt and iterations.
|
(default: SHA1) to derive a key of the requested length from the password,
|
||||||
The callback gets two arguments `(err, derivedKey)`.
|
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.
|
Synchronous PBKDF2 function. Returns derivedKey or throws error.
|
||||||
|
|
||||||
|
@ -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))
|
if (!util.isFunction(callback))
|
||||||
throw new Error('No callback provided to pbkdf2');
|
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) {
|
exports.pbkdf2Sync = function(password, salt, iterations, keylen, digest) {
|
||||||
return pbkdf2(password, salt, iterations, keylen);
|
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);
|
password = toBuf(password);
|
||||||
salt = toBuf(salt);
|
salt = toBuf(salt);
|
||||||
|
|
||||||
if (exports.DEFAULT_ENCODING === 'buffer')
|
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.
|
// at this point, we need to handle encodings.
|
||||||
var encoding = exports.DEFAULT_ENCODING;
|
var encoding = exports.DEFAULT_ENCODING;
|
||||||
if (callback) {
|
if (callback) {
|
||||||
binding.PBKDF2(password, salt, iterations, keylen, function(er, ret) {
|
function next(er, ret) {
|
||||||
if (ret)
|
if (ret)
|
||||||
ret = ret.toString(encoding);
|
ret = ret.toString(encoding);
|
||||||
callback(er, ret);
|
callback(er, ret);
|
||||||
});
|
}
|
||||||
|
binding.PBKDF2(password, salt, iterations, keylen, digest, next);
|
||||||
} else {
|
} else {
|
||||||
var ret = binding.PBKDF2(password, salt, iterations, keylen);
|
var ret = binding.PBKDF2(password, salt, iterations, keylen, digest);
|
||||||
return ret.toString(encoding);
|
return ret.toString(encoding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3468,6 +3468,7 @@ class PBKDF2Request : public AsyncWrap {
|
|||||||
public:
|
public:
|
||||||
PBKDF2Request(Environment* env,
|
PBKDF2Request(Environment* env,
|
||||||
Local<Object> object,
|
Local<Object> object,
|
||||||
|
const EVP_MD* digest,
|
||||||
ssize_t passlen,
|
ssize_t passlen,
|
||||||
char* pass,
|
char* pass,
|
||||||
ssize_t saltlen,
|
ssize_t saltlen,
|
||||||
@ -3475,6 +3476,7 @@ class PBKDF2Request : public AsyncWrap {
|
|||||||
ssize_t iter,
|
ssize_t iter,
|
||||||
ssize_t keylen)
|
ssize_t keylen)
|
||||||
: AsyncWrap(env, object),
|
: AsyncWrap(env, object),
|
||||||
|
digest_(digest),
|
||||||
error_(0),
|
error_(0),
|
||||||
passlen_(passlen),
|
passlen_(passlen),
|
||||||
pass_(pass),
|
pass_(pass),
|
||||||
@ -3495,6 +3497,10 @@ class PBKDF2Request : public AsyncWrap {
|
|||||||
return &work_req_;
|
return &work_req_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline const EVP_MD* digest() const {
|
||||||
|
return digest_;
|
||||||
|
}
|
||||||
|
|
||||||
inline ssize_t passlen() const {
|
inline ssize_t passlen() const {
|
||||||
return passlen_;
|
return passlen_;
|
||||||
}
|
}
|
||||||
@ -3544,6 +3550,7 @@ class PBKDF2Request : public AsyncWrap {
|
|||||||
uv_work_t work_req_;
|
uv_work_t work_req_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
const EVP_MD* digest_;
|
||||||
int error_;
|
int error_;
|
||||||
ssize_t passlen_;
|
ssize_t passlen_;
|
||||||
char* pass_;
|
char* pass_;
|
||||||
@ -3556,12 +3563,13 @@ class PBKDF2Request : public AsyncWrap {
|
|||||||
|
|
||||||
|
|
||||||
void EIO_PBKDF2(PBKDF2Request* req) {
|
void EIO_PBKDF2(PBKDF2Request* req) {
|
||||||
req->set_error(PKCS5_PBKDF2_HMAC_SHA1(
|
req->set_error(PKCS5_PBKDF2_HMAC(
|
||||||
req->pass(),
|
req->pass(),
|
||||||
req->passlen(),
|
req->passlen(),
|
||||||
reinterpret_cast<unsigned char*>(req->salt()),
|
reinterpret_cast<unsigned char*>(req->salt()),
|
||||||
req->saltlen(),
|
req->saltlen(),
|
||||||
req->iter(),
|
req->iter(),
|
||||||
|
req->digest(),
|
||||||
req->keylen(),
|
req->keylen(),
|
||||||
reinterpret_cast<unsigned char*>(req->key())));
|
reinterpret_cast<unsigned char*>(req->key())));
|
||||||
memset(req->pass(), 0, req->passlen());
|
memset(req->pass(), 0, req->passlen());
|
||||||
@ -3606,6 +3614,7 @@ void PBKDF2(const FunctionCallbackInfo<Value>& args) {
|
|||||||
HandleScope handle_scope(args.GetIsolate());
|
HandleScope handle_scope(args.GetIsolate());
|
||||||
Environment* env = Environment::GetCurrent(args.GetIsolate());
|
Environment* env = Environment::GetCurrent(args.GetIsolate());
|
||||||
|
|
||||||
|
const EVP_MD* digest = NULL;
|
||||||
const char* type_error = NULL;
|
const char* type_error = NULL;
|
||||||
char* pass = NULL;
|
char* pass = NULL;
|
||||||
char* salt = NULL;
|
char* salt = NULL;
|
||||||
@ -3618,7 +3627,7 @@ void PBKDF2(const FunctionCallbackInfo<Value>& args) {
|
|||||||
PBKDF2Request* req = NULL;
|
PBKDF2Request* req = NULL;
|
||||||
Local<Object> obj;
|
Local<Object> obj;
|
||||||
|
|
||||||
if (args.Length() != 4 && args.Length() != 5) {
|
if (args.Length() != 5 && args.Length() != 6) {
|
||||||
type_error = "Bad parameter";
|
type_error = "Bad parameter";
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
@ -3673,11 +3682,32 @@ void PBKDF2(const FunctionCallbackInfo<Value>& args) {
|
|||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
obj = Object::New();
|
if (args[4]->IsString()) {
|
||||||
req = new PBKDF2Request(env, obj, passlen, pass, saltlen, salt, iter, keylen);
|
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()) {
|
if (digest == NULL) {
|
||||||
obj->Set(env->ondone_string(), args[4]);
|
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.
|
// XXX(trevnorris): This will need to go with the rest of domains.
|
||||||
if (env->in_domain())
|
if (env->in_domain())
|
||||||
obj->Set(env->domain_string(), env->domain_array()->Get(0));
|
obj->Set(env->domain_string(), env->domain_array()->Get(0));
|
||||||
|
@ -912,6 +912,19 @@ testPBKDF2('pass\0word', 'sa\0lt', 4096, 16,
|
|||||||
'\x56\xfa\x6a\xa7\x55\x48\x09\x9d\xcc\x37\xd7\xf0\x34' +
|
'\x56\xfa\x6a\xa7\x55\x48\x09\x9d\xcc\x37\xd7\xf0\x34' +
|
||||||
'\x25\xe0\xc3');
|
'\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) {
|
function assertSorted(list) {
|
||||||
assert.deepEqual(list, list.sort());
|
assert.deepEqual(list, list.sort());
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user