Skip to content

Commit 37f51c1

Browse files
author
Eric Elliott
committed
API fix and documentation updates.
1 parent 4ccc934 commit 37f51c1

File tree

4 files changed

+88
-55
lines changed

4 files changed

+88
-55
lines changed

README.md

+21-15
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,26 @@ pw.verify(storedHash, userInput, function (err, isValid) {
4141

4242
## API
4343

44-
### .hash()
44+
### .hash(password, callback) callback(err, hashJSON)
4545

46-
`.hash(password, callback)`
47-
`callback(err, hash) // 'salt$hash'`
46+
Takes a new password and creates a unique hash. Passes a JSON encoded object to the callback.
4847

49-
Takes a new password and creates a unique salt and hash combination in the form `'salt$hash'`, suitable for storing in a single text field.
48+
* @param {[type]} password
49+
* @param {Function} callback
5050

51-
* @param {String} password A password to hash encode.
5251

52+
`callback`
53+
54+
* @param {Error} Error Error or null
55+
* @param {JSON} hashObject
56+
* @param {String} hashObject.hash
57+
* @param {String} hashObject.salt
58+
* @param {Number} hashObject.keyLength Bytes in hash
59+
* @param {String} hashObject.hashMethod
60+
* @param {Number} hashObject.workUnits
5361

54-
### .verify()
5562

56-
`.verify(hash, input, callback)`
57-
`callback(err, isValid)`
63+
### .verify(hash, input, callback) callback(err, isValid)
5864

5965
Takes a stored hash, password input from the user, and a callback, and determines whether or not the user's input matches the stored password.
6066

@@ -65,15 +71,15 @@ Takes a stored hash, password input from the user, and a callback, and determine
6571

6672
### .configure(options)
6773

68-
Alter defaults for `keylength` or `iterations`.
74+
Alter settings or set your secret workKey. Workkey is a secret value between one and 999, required to verify passwords. This secret makes it harder to brute force passwords from a stolen database by obscuring the number of iterations required to test passwords.
6975

70-
**Warning:** Decreasing these values can make your password
71-
database less secure.
76+
Warning: Decreasing `keyLength` or `work units` can make your password database less secure.
7277

73-
* @param {Object} options
74-
* @param {Number} options.keylength
75-
* @param {Number} options.iterations
76-
* @return {Object} credential the credential object
78+
* @param {Object} options Options object.
79+
* @param {Number} options.keyLength
80+
* @param {Number} options.workUnits
81+
* @param {Number} options.workKey secret
82+
* @return {Object} credential object
7783

7884

7985
## Motivation

credential.js

+57-31
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
/**
22
* credential
33
*
4-
* Fortify your user's passwords against rainbow table,
5-
* brute force, and variable hash time attacks using Node's
6-
* built in crypto functions.
4+
* Easy password hashing and verification in Node.
5+
* Protects against brute force, rainbow tables, and
6+
* timing attacks.
77
*
8-
* Employs cryptographically secure, password unique salts to
9-
* prevent rainbow table attacks.
8+
* Cryptographically secure per-password salts prevent
9+
* rainbow table attacks.
1010
*
11-
* Key stretching is used to make brute force attacks
12-
* impractical.
11+
* Variable work unit key stretching prevents brute force.
12+
*
13+
* Constant time verification prevents hang man timing
14+
* attacks.
1315
*
1416
* Created by Eric Elliott for the book,
1517
* "Programming JavaScript Applications" (O'Reilly)
@@ -32,13 +34,17 @@ var crypto = require('crypto'),
3234
* crypto.pbkdf2().
3335
*
3436
* Internet Engineering Task Force's RFC 2898
37+
*
3538
* @param {[type]} password
3639
* @param {[type]} salt
3740
* @param {Function} callback err, buffer
3841
*/
39-
pbkdf2 = function pbkdf2(password, salt, iterations, keylength, callback) {
42+
pbkdf2 = function pbkdf2(password, salt, workUnits, workKey, keyLength, callback) {
43+
var baseline = 1000,
44+
iterations = (baseline + workKey) * workUnits;
45+
4046
crypto.pbkdf2(password, salt,
41-
iterations, keylength, function (err, hash) {
47+
iterations, keyLength, function (err, hash) {
4248
if (err) {
4349
return callback(err);
4450
}
@@ -57,11 +63,12 @@ var crypto = require('crypto'),
5763
* use as a password salt using Node's built-in
5864
* crypto.randomBytes().
5965
*
66+
* @param {Numbre} keyLength Number of bytes.
6067
* @param {Function} callback [description]
6168
* @return {[type]} [description]
6269
*/
63-
createSalt = function createSalt(keylength, callback) {
64-
crypto.randomBytes(keylength, function (err, buff) {
70+
createSalt = function createSalt(keyLength, callback) {
71+
crypto.randomBytes(keyLength, function (err, buff) {
6572
if (err) {
6673
return callback(err);
6774
}
@@ -72,40 +79,50 @@ var crypto = require('crypto'),
7279
/**
7380
* toHash(password, callback)
7481
*
75-
* Takes a new password and creates a unique salt and hash
76-
* combination in the form `salt$hash`, suitable for storing
77-
* in a single text field.
82+
* Takes a new password and creates a unique hash. Passes
83+
* a JSON encoded object to the callback.
7884
*
7985
* @param {[type]} password
8086
* @param {Function} callback
8187
*/
88+
/**
89+
* callback
90+
* @param {Error} Error Error or null
91+
* @param {JSON} hashObject
92+
* @param {String} hashObject.hash
93+
* @param {String} hashObject.salt
94+
* @param {Number} hashObject.keyLength Bytes in hash
95+
* @param {String} hashObject.hashMethod
96+
* @param {Number} hashObject.workUnits
97+
*/
8298
toHash = function toHash(password,
8399
callback) {
84100
var hashMethod = this.hashMethod,
85-
keylength = this.keylength,
86-
iterations = this.iterations;
101+
keyLength = this.keyLength,
102+
workUnits = this.workUnits,
103+
workKey = this.workKey;
87104

88105
// Create the salt
89-
createSalt(keylength, function (err, salt) {
106+
createSalt(keyLength, function (err, salt) {
90107
if (err) {
91108
return callback(err);
92109
}
93110

94111
// Then create the hash
95112
hashMethods[hashMethod](password, salt,
96-
iterations, keylength,
113+
workUnits, workKey, keyLength,
97114
function (err, hash) {
98115

99116
if (err) {
100117
return callback(err);
101118
}
102119

103120
callback(null, JSON.stringify({
104-
salt: salt,
105121
hash: hash,
122+
salt: salt,
123+
keyLength: keyLength,
106124
hashMethod: hashMethod,
107-
iterations: iterations,
108-
keylength: keylength
125+
workUnits: workUnits
109126
}));
110127

111128
});
@@ -152,15 +169,17 @@ var crypto = require('crypto'),
152169
* @param {Function} callback callback(err, isValid)
153170
*/
154171
verify = function verify(hash, input, callback) {
155-
var storedHash = parseHash(hash);
172+
var storedHash = parseHash(hash),
173+
workKey = this.workKey;
156174

157175
if (!hashMethods[storedHash.hashMethod]) {
158176
return callback(new Error('Couldn\'t parse stored ' +
159177
'hash.'));
160178
}
161179

162-
hashMethods[storedHash.hashMethod](input, storedHash.salt, storedHash.iterations,
163-
storedHash.keylength, function (err, newHash) {
180+
hashMethods[storedHash.hashMethod](input, storedHash.salt,
181+
storedHash.workUnits, workKey, storedHash.keyLength,
182+
function (err, newHash) {
164183

165184
var result;
166185
if (err) {
@@ -173,13 +192,19 @@ var crypto = require('crypto'),
173192
/**
174193
* configure(options)
175194
*
176-
* Alter defaults for `keylength` or `iterations`.
177-
* Warning: Decreasing these values can make your password
178-
* database less secure.
195+
* Alter settings or set your secret workKey. Workkey
196+
* is a secret value between one and 999, required to verify
197+
* passwords. This secret makes it harder to brute force
198+
* passwords from a stolen database by obscuring the number
199+
* of iterations required to test passwords.
200+
*
201+
* Warning: Decreasing `keyLength` or `work units`
202+
* can make your password database less secure.
179203
*
180204
* @param {Object} options Options object.
181-
* @param {Number} options.keylength
182-
* @param {Number} options.iterations
205+
* @param {Number} options.keyLength
206+
* @param {Number} options.workUnits
207+
* @param {Number} options.workKey secret
183208
* @return {Object} credential object
184209
*/
185210
configure = function configure(options) {
@@ -188,8 +213,9 @@ var crypto = require('crypto'),
188213
},
189214

190215
defaults = {
191-
keylength: 66,
192-
iterations: 80000,
216+
keyLength: 66,
217+
workUnits: 60,
218+
workKey: parseInt(process.env.credential_key, 10) || 388,
193219
hashMethod: 'pbkdf2'
194220
};
195221

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "credential",
3-
"version": "0.2.0",
3+
"version": "0.2.1",
44
"description": "Easy password hashing and verification in Node. Protects against brute force, rainbow tables, and timing attacks.",
55
"main": "credential.js",
66
"directories": {

test/credential-test.js

+9-8
Original file line numberDiff line numberDiff line change
@@ -89,21 +89,22 @@ test('verify with wrong pw', function (t) {
8989

9090
});
9191

92-
9392
test('overrides', function (t) {
94-
var iterations = 1;
95-
var keylength = 12;
93+
var workUnits = 60;
94+
var workKey = 463;
95+
var keyLength = 12;
9696
pw.configure({
97-
iterations: iterations,
98-
keylength: keylength
97+
workUnits: workUnits,
98+
workKey: workKey,
99+
keyLength: keyLength
99100
});
100101

101102
pw.hash('foo', function (err, hash) {
102103

103-
t.equal(pw.iterations, iterations,
104-
'should allow iterations override');
104+
t.equal(pw.workUnits, workUnits,
105+
'should allow workUnits override');
105106

106-
t.equal(JSON.parse(hash).keylength, keylength,
107+
t.equal(JSON.parse(hash).keyLength, keyLength,
107108
'should allow keylength override');
108109
t.end();
109110
});

0 commit comments

Comments
 (0)