1
1
/**
2
2
* credential
3
3
*
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 .
7
7
*
8
- * Employs cryptographically secure, password unique salts to
9
- * prevent rainbow table attacks.
8
+ * Cryptographically secure per- password salts prevent
9
+ * rainbow table attacks.
10
10
*
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.
13
15
*
14
16
* Created by Eric Elliott for the book,
15
17
* "Programming JavaScript Applications" (O'Reilly)
@@ -32,13 +34,17 @@ var crypto = require('crypto'),
32
34
* crypto.pbkdf2().
33
35
*
34
36
* Internet Engineering Task Force's RFC 2898
37
+ *
35
38
* @param {[type] } password
36
39
* @param {[type] } salt
37
40
* @param {Function } callback err, buffer
38
41
*/
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
+
40
46
crypto . pbkdf2 ( password , salt ,
41
- iterations , keylength , function ( err , hash ) {
47
+ iterations , keyLength , function ( err , hash ) {
42
48
if ( err ) {
43
49
return callback ( err ) ;
44
50
}
@@ -57,11 +63,12 @@ var crypto = require('crypto'),
57
63
* use as a password salt using Node's built-in
58
64
* crypto.randomBytes().
59
65
*
66
+ * @param {Numbre } keyLength Number of bytes.
60
67
* @param {Function } callback [description]
61
68
* @return {[type] } [description]
62
69
*/
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 ) {
65
72
if ( err ) {
66
73
return callback ( err ) ;
67
74
}
@@ -72,40 +79,50 @@ var crypto = require('crypto'),
72
79
/**
73
80
* toHash(password, callback)
74
81
*
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.
78
84
*
79
85
* @param {[type] } password
80
86
* @param {Function } callback
81
87
*/
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
+ */
82
98
toHash = function toHash ( password ,
83
99
callback ) {
84
100
var hashMethod = this . hashMethod ,
85
- keylength = this . keylength ,
86
- iterations = this . iterations ;
101
+ keyLength = this . keyLength ,
102
+ workUnits = this . workUnits ,
103
+ workKey = this . workKey ;
87
104
88
105
// Create the salt
89
- createSalt ( keylength , function ( err , salt ) {
106
+ createSalt ( keyLength , function ( err , salt ) {
90
107
if ( err ) {
91
108
return callback ( err ) ;
92
109
}
93
110
94
111
// Then create the hash
95
112
hashMethods [ hashMethod ] ( password , salt ,
96
- iterations , keylength ,
113
+ workUnits , workKey , keyLength ,
97
114
function ( err , hash ) {
98
115
99
116
if ( err ) {
100
117
return callback ( err ) ;
101
118
}
102
119
103
120
callback ( null , JSON . stringify ( {
104
- salt : salt ,
105
121
hash : hash ,
122
+ salt : salt ,
123
+ keyLength : keyLength ,
106
124
hashMethod : hashMethod ,
107
- iterations : iterations ,
108
- keylength : keylength
125
+ workUnits : workUnits
109
126
} ) ) ;
110
127
111
128
} ) ;
@@ -152,15 +169,17 @@ var crypto = require('crypto'),
152
169
* @param {Function } callback callback(err, isValid)
153
170
*/
154
171
verify = function verify ( hash , input , callback ) {
155
- var storedHash = parseHash ( hash ) ;
172
+ var storedHash = parseHash ( hash ) ,
173
+ workKey = this . workKey ;
156
174
157
175
if ( ! hashMethods [ storedHash . hashMethod ] ) {
158
176
return callback ( new Error ( 'Couldn\'t parse stored ' +
159
177
'hash.' ) ) ;
160
178
}
161
179
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 ) {
164
183
165
184
var result ;
166
185
if ( err ) {
@@ -173,13 +192,19 @@ var crypto = require('crypto'),
173
192
/**
174
193
* configure(options)
175
194
*
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.
179
203
*
180
204
* @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
183
208
* @return {Object } credential object
184
209
*/
185
210
configure = function configure ( options ) {
@@ -188,8 +213,9 @@ var crypto = require('crypto'),
188
213
} ,
189
214
190
215
defaults = {
191
- keylength : 66 ,
192
- iterations : 80000 ,
216
+ keyLength : 66 ,
217
+ workUnits : 60 ,
218
+ workKey : parseInt ( process . env . credential_key , 10 ) || 388 ,
193
219
hashMethod : 'pbkdf2'
194
220
} ;
195
221
0 commit comments