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 */
|
||||
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.
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user