Skip to content

Commit 4d96794

Browse files
committed
v2.0.0
1 parent c87c913 commit 4d96794

File tree

5 files changed

+116
-42
lines changed

5 files changed

+116
-42
lines changed

README.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This module will change and do the following things for you:
77
* Input values (passwords, usually) are expected in utf-8.
88
* Output/hash values are base64-encoded, and can be stored directly in your data store of choice.
99
* Scrypt parameters are set to `scrypt.params(0.1)`, this can be overridden on a per-hash basis (see API documentation below).
10-
* 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.
10+
* 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.
1111

1212
The API supports both Promises and nodebacks.
1313

@@ -87,6 +87,12 @@ scrypt.hash("secretpassword", {}, function(err, hash){
8787
});
8888
```
8989

90+
## Upgrading to 2.0.0
91+
92+
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).
93+
94+
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.
95+
9096
## API
9197

9298
### scrypt.hash(input, [options, [callback]])
@@ -95,7 +101,7 @@ Creates a hash.
95101

96102
* __input__: The input to hash, usually a password.
97103
* __options__: *Optional.* Custom options.
98-
* __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).
104+
* __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).
99105
* __callback__: *Optional.* A nodeback to call upon completion. If omitted, the function will return a Promise.
100106

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

lib/scrypt-for-humans.coffee

+52-18
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,79 @@ scrypt = require "scrypt"
22
errors = require "errors"
33
Promise = require "bluebird"
44

5-
# Scrypt input/output format configuration
6-
# 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.
7-
scrypt.hash.config.keyEncoding = "utf8"
8-
scrypt.hash.config.outputEncoding = "base64"
9-
scrypt.verify.config.keyEncoding = "utf8"
10-
scrypt.verify.config.hashEncoding = "base64"
11-
125
# Some custom error types, since the `scrypt` library doesn't have proper error handling
136
errors.create name: "ScryptError"
147
errors.create {name: "ScryptInputError", parents: errors.ScryptError}
158
errors.create {name: "ScryptPasswordError", parents: errors.ScryptError}
169
errors.create {name: "ScryptInternalError", parents: errors.ScryptError}
1710

11+
scryptErrorMap = {
12+
"getrlimit or sysctl(hw.usermem) failed": 1
13+
"clock_getres or clock_gettime failed": 2
14+
"error computing derived key": 3
15+
"could not read salt from /dev/urandom": 4
16+
"error in OpenSSL": 5
17+
"malloc failed": 6
18+
"data is not a valid scrypt-encrypted block": 7
19+
"unrecognized scrypt format": 8
20+
"decrypting file would take too much memory": 9
21+
"decrypting file would take too long": 10
22+
"password is incorrect": 11
23+
"error writing output file": 12
24+
"error reading input file": 13
25+
"error unkown": -1
26+
}
27+
28+
defaultParameters = Promise.promisify(scrypt.params)(0.1, undefined, undefined)
29+
30+
getDefaultParameters = (params) ->
31+
# 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.
32+
if params?
33+
return params
34+
else
35+
return defaultParameters
36+
37+
normalizePassword = (password) ->
38+
if Buffer.isBuffer(password)
39+
return password
40+
else
41+
return new Buffer(password)
1842

1943
scryptHandler = (resolve, reject) ->
20-
# This is ridiculous, but `scrypt` doesn't have proper error-handling facilities...
44+
# Well, `scrypt` now returns real Error objects. Except now they don't have error codes anymore...
2145
return (err, result) ->
2246
if err?
23-
errorObj = switch err.scrypt_err_code
24-
when 1, 2, 3, 4, 5, 6, 9, 10, 12, 13 then errors.ScryptInternalError
47+
errorObj = switch scryptErrorMap[err.message]
48+
when 1, 2, 3, 4, 5, 6, 9, 10, 12, 13, -1 then errors.ScryptInternalError
2549
when 7, 8 then errors.ScryptInputError
2650
when 11 then errors.ScryptPasswordError
27-
reject new errorObj(err.scrypt_err_message)
28-
else
51+
reject new errorObj(err.message)
52+
else if result == true
2953
resolve result
30-
54+
else if result == false
55+
reject new errors.ScryptPasswordError("The password did not match.")
56+
else
57+
resolve result.toString("base64")
3158

3259
module.exports =
3360
hash: (password, options = {}, callback) ->
34-
(new Promise (resolve, reject) ->
35-
options.params ?= scrypt.params(0.1)
36-
scrypt.hash password, options.params, scryptHandler(resolve, reject)
37-
).nodeify(callback)
61+
# 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...
62+
Promise.try ->
63+
getDefaultParameters(options.params)
64+
.then (parameters) ->
65+
new Promise (resolve, reject) ->
66+
scrypt.kdf normalizePassword(password), parameters, scryptHandler(resolve, reject)
67+
.nodeify(callback)
68+
3869
verifyHash: (password, hash, callback) ->
3970
(new Promise (resolve, reject) ->
40-
scrypt.verify hash, password, scryptHandler(resolve, reject)
71+
hashBuffer = new Buffer(hash, "base64")
72+
scrypt.verifyKdf hashBuffer, normalizePassword(password), scryptHandler(resolve, reject)
4173
).nodeify(callback)
74+
4275
ScryptError: errors.ScryptError
4376
InputError: errors.ScryptInputError
4477
PasswordError: errors.ScryptPasswordError
4578
InternalError: errors.ScryptInternalError
79+
4680
scryptLib: scrypt

lib/scrypt-for-humans.js

+54-19
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,11 @@
1-
var Promise, errors, scrypt, scryptHandler;
1+
var Promise, defaultParameters, errors, getDefaultParameters, normalizePassword, scrypt, scryptErrorMap, scryptHandler;
22

33
scrypt = require("scrypt");
44

55
errors = require("errors");
66

77
Promise = require("bluebird");
88

9-
scrypt.hash.config.keyEncoding = "utf8";
10-
11-
scrypt.hash.config.outputEncoding = "base64";
12-
13-
scrypt.verify.config.keyEncoding = "utf8";
14-
15-
scrypt.verify.config.hashEncoding = "base64";
16-
179
errors.create({
1810
name: "ScryptError"
1911
});
@@ -33,12 +25,47 @@ errors.create({
3325
parents: errors.ScryptError
3426
});
3527

28+
scryptErrorMap = {
29+
"getrlimit or sysctl(hw.usermem) failed": 1,
30+
"clock_getres or clock_gettime failed": 2,
31+
"error computing derived key": 3,
32+
"could not read salt from /dev/urandom": 4,
33+
"error in OpenSSL": 5,
34+
"malloc failed": 6,
35+
"data is not a valid scrypt-encrypted block": 7,
36+
"unrecognized scrypt format": 8,
37+
"decrypting file would take too much memory": 9,
38+
"decrypting file would take too long": 10,
39+
"password is incorrect": 11,
40+
"error writing output file": 12,
41+
"error reading input file": 13,
42+
"error unkown": -1
43+
};
44+
45+
defaultParameters = Promise.promisify(scrypt.params)(0.1, void 0, void 0);
46+
47+
getDefaultParameters = function(params) {
48+
if (params != null) {
49+
return params;
50+
} else {
51+
return defaultParameters;
52+
}
53+
};
54+
55+
normalizePassword = function(password) {
56+
if (Buffer.isBuffer(password)) {
57+
return password;
58+
} else {
59+
return new Buffer(password);
60+
}
61+
};
62+
3663
scryptHandler = function(resolve, reject) {
3764
return function(err, result) {
3865
var errorObj;
3966
if (err != null) {
4067
errorObj = (function() {
41-
switch (err.scrypt_err_code) {
68+
switch (scryptErrorMap[err.message]) {
4269
case 1:
4370
case 2:
4471
case 3:
@@ -49,6 +76,7 @@ scryptHandler = function(resolve, reject) {
4976
case 10:
5077
case 12:
5178
case 13:
79+
case -1:
5280
return errors.ScryptInternalError;
5381
case 7:
5482
case 8:
@@ -57,9 +85,13 @@ scryptHandler = function(resolve, reject) {
5785
return errors.ScryptPasswordError;
5886
}
5987
})();
60-
return reject(new errorObj(err.scrypt_err_message));
61-
} else {
88+
return reject(new errorObj(err.message));
89+
} else if (result === true) {
6290
return resolve(result);
91+
} else if (result === false) {
92+
return reject(new errors.ScryptPasswordError("The password did not match."));
93+
} else {
94+
return resolve(result.toString("base64"));
6395
}
6496
};
6597
};
@@ -69,16 +101,19 @@ module.exports = {
69101
if (options == null) {
70102
options = {};
71103
}
72-
return (new Promise(function(resolve, reject) {
73-
if (options.params == null) {
74-
options.params = scrypt.params(0.1);
75-
}
76-
return scrypt.hash(password, options.params, scryptHandler(resolve, reject));
77-
})).nodeify(callback);
104+
return Promise["try"](function() {
105+
return getDefaultParameters(options.params);
106+
}).then(function(parameters) {
107+
return new Promise(function(resolve, reject) {
108+
return scrypt.kdf(normalizePassword(password), parameters, scryptHandler(resolve, reject));
109+
});
110+
}).nodeify(callback);
78111
},
79112
verifyHash: function(password, hash, callback) {
80113
return (new Promise(function(resolve, reject) {
81-
return scrypt.verify(hash, password, scryptHandler(resolve, reject));
114+
var hashBuffer;
115+
hashBuffer = new Buffer(hash, "base64");
116+
return scrypt.verifyKdf(hashBuffer, normalizePassword(password), scryptHandler(resolve, reject));
82117
})).nodeify(callback);
83118
},
84119
ScryptError: errors.ScryptError,

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "scrypt-for-humans",
3-
"version": "1.0.2",
3+
"version": "2.0.0",
44
"description": "A human-friendly API wrapper for the Node.js Scrypt bindings.",
55
"main": "index.js",
66
"scripts": {
@@ -31,6 +31,6 @@
3131
"dependencies": {
3232
"bluebird": "^2.6.4",
3333
"errors": "^0.2.0",
34-
"scrypt": "^4.0.7"
34+
"scrypt": "^5.2.0"
3535
}
3636
}

test.js

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ var scrypt = require("./");
22
var Promise = require("bluebird");
33

44
/* Using Promises */
5-
65
var theHash;
76

87
Promise.try(function(){

0 commit comments

Comments
 (0)