master
Sven Slootweg 9 years ago
parent c87c9132da
commit 4d96794ab9

@ -7,7 +7,7 @@ This module will change and do the following things for you:
* Input values (passwords, usually) are expected in utf-8.
* Output/hash values are base64-encoded, and can be stored directly in your data store of choice.
* Scrypt parameters are set to `scrypt.params(0.1)`, this can be overridden on a per-hash basis (see API documentation below).
* Scrypt errors, which are not proper Error types in the original library, are caught and rethrown as one of three correctly-inheriting Error types (see API documentation below). This means you can handle them like any other kind of Error.
* Scrypt errors, which are now proper Error types in the original library but still not easily distinguishable, are caught and rethrown as one of three correctly-inheriting Error types (see API documentation below). This means you can handle them like any other kind of Error.
The API supports both Promises and nodebacks.
@ -87,6 +87,12 @@ scrypt.hash("secretpassword", {}, function(err, hash){
});
```
## Upgrading to 2.0.0
Due to changes in the underlying `scrypt` library, there has been a minor indirect change in our documented API as well. Specifically, `scrypt.scryptLib.params` is now asynchronous by default, with (poor) support for ES6 Promises. The new documentation can be found [here](https://github.com/barrysteyn/node-scrypt/blob/master/Readme.md#params). Due to its inconsistent behaviour, I recommend manual promisification using [Bluebird](https://www.npmjs.com/package/bluebird) or [`es6-promisify`](https://www.npmjs.com/package/es6-promisify).
The other changes in `scrypt` do not affect the `scrypt-for-humans` API, other than introducing support for Node.js 4. If you were not using custom `params`, you can remain using `scrypt-for-humans` like you have done previously.
## API
### scrypt.hash(input, [options, [callback]])
@ -95,7 +101,7 @@ Creates a hash.
* __input__: The input to hash, usually a password.
* __options__: *Optional.* Custom options.
* __options.params__: Sets the Scrypt parameters to use. Defaults to `scrypt.params(0.1)`. If you want to change these, you'll probably need scrypt.scryptLib (documented below).
* __options.params__: Sets the Scrypt parameters to use. Defaults to `scrypt.params(0.1)`. If you want to change these, you'll probably need `scrypt.scryptLib` (documented below).
* __callback__: *Optional.* A nodeback to call upon completion. If omitted, the function will return a Promise.
If this is successful, the hash is returned as either the resolved Promise value or the second callback parameter, depending on the API you use.

@ -2,45 +2,79 @@ scrypt = require "scrypt"
errors = require "errors"
Promise = require "bluebird"
# Scrypt input/output format configuration
# FIXME: Figure out how to isolate this, so that there is a guarantee these changes won't affect any other `scrypt` imports outside of the module.
scrypt.hash.config.keyEncoding = "utf8"
scrypt.hash.config.outputEncoding = "base64"
scrypt.verify.config.keyEncoding = "utf8"
scrypt.verify.config.hashEncoding = "base64"
# Some custom error types, since the `scrypt` library doesn't have proper error handling
errors.create name: "ScryptError"
errors.create {name: "ScryptInputError", parents: errors.ScryptError}
errors.create {name: "ScryptPasswordError", parents: errors.ScryptError}
errors.create {name: "ScryptInternalError", parents: errors.ScryptError}
scryptErrorMap = {
"getrlimit or sysctl(hw.usermem) failed": 1
"clock_getres or clock_gettime failed": 2
"error computing derived key": 3
"could not read salt from /dev/urandom": 4
"error in OpenSSL": 5
"malloc failed": 6
"data is not a valid scrypt-encrypted block": 7
"unrecognized scrypt format": 8
"decrypting file would take too much memory": 9
"decrypting file would take too long": 10
"password is incorrect": 11
"error writing output file": 12
"error reading input file": 13
"error unkown": -1
}
defaultParameters = Promise.promisify(scrypt.params)(0.1, undefined, undefined)
getDefaultParameters = (params) ->
# This wrapper function is to ensure that we only calculate the parameters once, but can still skip waiting for that if custom parameters were passed in anyway.
if params?
return params
else
return defaultParameters
normalizePassword = (password) ->
if Buffer.isBuffer(password)
return password
else
return new Buffer(password)
scryptHandler = (resolve, reject) ->
# This is ridiculous, but `scrypt` doesn't have proper error-handling facilities...
# Well, `scrypt` now returns real Error objects. Except now they don't have error codes anymore...
return (err, result) ->
if err?
errorObj = switch err.scrypt_err_code
when 1, 2, 3, 4, 5, 6, 9, 10, 12, 13 then errors.ScryptInternalError
errorObj = switch scryptErrorMap[err.message]
when 1, 2, 3, 4, 5, 6, 9, 10, 12, 13, -1 then errors.ScryptInternalError
when 7, 8 then errors.ScryptInputError
when 11 then errors.ScryptPasswordError
reject new errorObj(err.scrypt_err_message)
else
reject new errorObj(err.message)
else if result == true
resolve result
else if result == false
reject new errors.ScryptPasswordError("The password did not match.")
else
resolve result.toString("base64")
module.exports =
hash: (password, options = {}, callback) ->
(new Promise (resolve, reject) ->
options.params ?= scrypt.params(0.1)
scrypt.hash password, options.params, scryptHandler(resolve, reject)
).nodeify(callback)
# We will still manually promisify, because the behaviour of `scrypt` is not predictable. It may either synchronously throw an error or return a Promise, depending on available ECMAScript features...
Promise.try ->
getDefaultParameters(options.params)
.then (parameters) ->
new Promise (resolve, reject) ->
scrypt.kdf normalizePassword(password), parameters, scryptHandler(resolve, reject)
.nodeify(callback)
verifyHash: (password, hash, callback) ->
(new Promise (resolve, reject) ->
scrypt.verify hash, password, scryptHandler(resolve, reject)
hashBuffer = new Buffer(hash, "base64")
scrypt.verifyKdf hashBuffer, normalizePassword(password), scryptHandler(resolve, reject)
).nodeify(callback)
ScryptError: errors.ScryptError
InputError: errors.ScryptInputError
PasswordError: errors.ScryptPasswordError
InternalError: errors.ScryptInternalError
scryptLib: scrypt

@ -1,4 +1,4 @@
var Promise, errors, scrypt, scryptHandler;
var Promise, defaultParameters, errors, getDefaultParameters, normalizePassword, scrypt, scryptErrorMap, scryptHandler;
scrypt = require("scrypt");
@ -6,14 +6,6 @@ errors = require("errors");
Promise = require("bluebird");
scrypt.hash.config.keyEncoding = "utf8";
scrypt.hash.config.outputEncoding = "base64";
scrypt.verify.config.keyEncoding = "utf8";
scrypt.verify.config.hashEncoding = "base64";
errors.create({
name: "ScryptError"
});
@ -33,12 +25,47 @@ errors.create({
parents: errors.ScryptError
});
scryptErrorMap = {
"getrlimit or sysctl(hw.usermem) failed": 1,
"clock_getres or clock_gettime failed": 2,
"error computing derived key": 3,
"could not read salt from /dev/urandom": 4,
"error in OpenSSL": 5,
"malloc failed": 6,
"data is not a valid scrypt-encrypted block": 7,
"unrecognized scrypt format": 8,
"decrypting file would take too much memory": 9,
"decrypting file would take too long": 10,
"password is incorrect": 11,
"error writing output file": 12,
"error reading input file": 13,
"error unkown": -1
};
defaultParameters = Promise.promisify(scrypt.params)(0.1, void 0, void 0);
getDefaultParameters = function(params) {
if (params != null) {
return params;
} else {
return defaultParameters;
}
};
normalizePassword = function(password) {
if (Buffer.isBuffer(password)) {
return password;
} else {
return new Buffer(password);
}
};
scryptHandler = function(resolve, reject) {
return function(err, result) {
var errorObj;
if (err != null) {
errorObj = (function() {
switch (err.scrypt_err_code) {
switch (scryptErrorMap[err.message]) {
case 1:
case 2:
case 3:
@ -49,6 +76,7 @@ scryptHandler = function(resolve, reject) {
case 10:
case 12:
case 13:
case -1:
return errors.ScryptInternalError;
case 7:
case 8:
@ -57,9 +85,13 @@ scryptHandler = function(resolve, reject) {
return errors.ScryptPasswordError;
}
})();
return reject(new errorObj(err.scrypt_err_message));
} else {
return reject(new errorObj(err.message));
} else if (result === true) {
return resolve(result);
} else if (result === false) {
return reject(new errors.ScryptPasswordError("The password did not match."));
} else {
return resolve(result.toString("base64"));
}
};
};
@ -69,16 +101,19 @@ module.exports = {
if (options == null) {
options = {};
}
return (new Promise(function(resolve, reject) {
if (options.params == null) {
options.params = scrypt.params(0.1);
}
return scrypt.hash(password, options.params, scryptHandler(resolve, reject));
})).nodeify(callback);
return Promise["try"](function() {
return getDefaultParameters(options.params);
}).then(function(parameters) {
return new Promise(function(resolve, reject) {
return scrypt.kdf(normalizePassword(password), parameters, scryptHandler(resolve, reject));
});
}).nodeify(callback);
},
verifyHash: function(password, hash, callback) {
return (new Promise(function(resolve, reject) {
return scrypt.verify(hash, password, scryptHandler(resolve, reject));
var hashBuffer;
hashBuffer = new Buffer(hash, "base64");
return scrypt.verifyKdf(hashBuffer, normalizePassword(password), scryptHandler(resolve, reject));
})).nodeify(callback);
},
ScryptError: errors.ScryptError,

@ -1,6 +1,6 @@
{
"name": "scrypt-for-humans",
"version": "1.0.2",
"version": "2.0.0",
"description": "A human-friendly API wrapper for the Node.js Scrypt bindings.",
"main": "index.js",
"scripts": {
@ -31,6 +31,6 @@
"dependencies": {
"bluebird": "^2.6.4",
"errors": "^0.2.0",
"scrypt": "^4.0.7"
"scrypt": "^5.2.0"
}
}

@ -2,7 +2,6 @@ var scrypt = require("./");
var Promise = require("bluebird");
/* Using Promises */
var theHash;
Promise.try(function(){

Loading…
Cancel
Save