forked from kelektiv/node.bcrypt.js
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbcrypt.js
More file actions
346 lines (288 loc) · 10.5 KB
/
bcrypt.js
File metadata and controls
346 lines (288 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
'use strict';
var nodePreGyp = require('@mapbox/node-pre-gyp');
var path = require('path');
var binding_path = nodePreGyp.find(path.resolve(path.join(__dirname, './package.json')));
var bindings = require(binding_path);
var crypto = require('crypto');
var promises = require('./promises');
/// generate a salt (sync)
/// @param {Number} [rounds] number of rounds (default 10)
/// @return {String} salt
module.exports.genSaltSync = function genSaltSync(rounds, minor) {
// default 10 rounds
if (!rounds) {
rounds = 10;
} else if (typeof rounds !== 'number') {
throw new Error('rounds must be a number');
}
if(!minor) {
minor = 'b';
} else if(minor !== 'b' && minor !== 'a') {
throw new Error('minor must be either "a" or "b"');
}
return bindings.gen_salt_sync(minor, rounds, crypto.randomBytes(16));
};
/// generate a salt
/// @param {Number} [rounds] number of rounds (default 10)
/// @param {Function} cb callback(err, salt)
module.exports.genSalt = function genSalt(rounds, minor, cb) {
var error;
// if callback is first argument, then use defaults for others
if (typeof arguments[0] === 'function') {
// have to set callback first otherwise arguments are overriden
cb = arguments[0];
rounds = 10;
minor = 'b';
// callback is second argument
} else if (typeof arguments[1] === 'function') {
// have to set callback first otherwise arguments are overriden
cb = arguments[1];
minor = 'b';
}
if (!cb) {
return promises.promise(genSalt, this, [rounds, minor]);
}
// default 10 rounds
if (!rounds) {
rounds = 10;
} else if (typeof rounds !== 'number') {
// callback error asynchronously
error = new Error('rounds must be a number');
return process.nextTick(function() {
cb(error);
});
}
if(!minor) {
minor = 'b'
} else if(minor !== 'b' && minor !== 'a') {
error = new Error('minor must be either "a" or "b"');
return process.nextTick(function() {
cb(error);
});
}
crypto.randomBytes(16, function(error, randomBytes) {
if (error) {
cb(error);
return;
}
bindings.gen_salt(minor, rounds, randomBytes, cb);
});
};
module.exports.genSaltByTime = function genSaltByTime(exptime, minor, cb) {
var error;
var rounds = 10;
// if callback is first argument, then use defaults for others
if (typeof arguments[0] === 'function') {
// have to set callback first otherwise arguments are overriden
cb = arguments[0];
exptime = 100;
minor = 'b';
// callback is second argument
} else if (typeof arguments[1] === 'function') {
// have to set callback first otherwise arguments are overriden
cb = arguments[1];
minor = 'b';
}
if (!cb) {
return promises.promise(genSaltByTime, this, [exptime, minor]);
}
// default 100 milliseconds and minimum 4 miliseconds
if (!exptime) {
exptime = 100;
} else if (exptime < 4) {
exptime = 4;
} else if (typeof exptime !== 'number') {
// callback error asynchronously
error = new Error('Expected time must be a number');
return process.nextTick(function() {
cb(error);
});
}
if (!minor) {
minor = 'b'
} else if (minor !== 'b' && minor !== 'a') {
error = new Error('minor must be either "a" or "b"');
return process.nextTick(function() {
cb(error);
});
}
crypto.randomBytes(16, function(error, randomBytes) {
if (error) {
cb(error);
return;
}
//since the relation b/w expected time and rounds roughly follows exptime = 2^(rounds-3)
//rounds is equal to log2(exptime)+3
rounds = Math.log(exptime)/Math.log(2);
rounds = Math.round(rounds)+3;
// for a secure hash, taking 4 as minimum rounds
rounds = Math.max(rounds, 4);
bindings.gen_salt(minor, rounds, randomBytes, cb);
});
};
/// hash data using a salt
/// @param {String|Buffer} data the data to encrypt
/// @param {String} salt the salt to use when hashing
/// @return {String} hash
module.exports.hashSync = function hashSync(data, salt) {
if (data == null || salt == null) {
throw new Error('data and salt arguments required');
}
if (!(typeof data === 'string' || data instanceof Buffer) || (typeof salt !== 'string' && typeof salt !== 'number')) {
throw new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds');
}
if (typeof salt === 'number') {
salt = module.exports.genSaltSync(salt);
}
return bindings.encrypt_sync(data, salt);
};
/// hash data using a salt
/// @param {String|Buffer} data the data to encrypt
/// @param {String} salt the salt to use when hashing
/// @param {Function} cb callback(err, hash)
module.exports.hash = function hash(data, salt, cb) {
var error;
if (typeof data === 'function') {
error = new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds');
return process.nextTick(function() {
data(error);
});
}
if (typeof salt === 'function') {
error = new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds');
return process.nextTick(function() {
salt(error);
});
}
// cb exists but is not a function
// return a rejecting promise
if (cb && typeof cb !== 'function') {
return promises.reject(new Error('cb must be a function or null to return a Promise'));
}
if (!cb) {
return promises.promise(hash, this, [data, salt]);
}
if (data == null || salt == null) {
error = new Error('data and salt arguments required');
return process.nextTick(function() {
cb(error);
});
}
if (!(typeof data === 'string' || data instanceof Buffer) || (typeof salt !== 'string' && typeof salt !== 'number')) {
error = new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds');
return process.nextTick(function() {
cb(error);
});
}
if (typeof salt === 'number') {
return module.exports.genSalt(salt, function(err, salt) {
return bindings.encrypt(data, salt, cb);
});
}
return bindings.encrypt(data, salt, cb);
};
module.exports.hashByTime = function hashByTime(data, salt, cb) {
var error;
if (typeof data === 'function') {
error = new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds');
return process.nextTick(function() {
data(error);
});
}
if (typeof salt === 'function') {
error = new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds');
return process.nextTick(function() {
salt(error);
});
}
// cb exists but is not a function
// return a rejecting promise
if (cb && typeof cb !== 'function') {
return promises.reject(new Error('cb must be a function or null to return a Promise'));
}
if (!cb) {
return promises.promise(hashByTime, this, [data, salt]);
}
if (data == null || salt == null) {
error = new Error('data and salt arguments required');
return process.nextTick(function() {
cb(error);
});
}
if (!(typeof data === 'string' || data instanceof Buffer) || (typeof salt !== 'string' && typeof salt !== 'number')) {
error = new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds');
return process.nextTick(function() {
cb(error);
});
}
if (typeof salt === 'number') {
return module.exports.genSaltByTime(salt, function(err, salt) {
return bindings.encrypt(data, salt, cb);
});
}
return bindings.encrypt(data, salt, cb);
};
/// compare raw data to hash
/// @param {String|Buffer} data the data to hash and compare
/// @param {String} hash expected hash
/// @return {bool} true if hashed data matches hash
module.exports.compareSync = function compareSync(data, hash) {
if (data == null || hash == null) {
throw new Error('data and hash arguments required');
}
if (!(typeof data === 'string' || data instanceof Buffer) || typeof hash !== 'string') {
throw new Error('data must be a string or Buffer and hash must be a string');
}
return bindings.compare_sync(data, hash);
};
/// compare raw data to hash
/// @param {String|Buffer} data the data to hash and compare
/// @param {String} hash expected hash
/// @param {Function} cb callback(err, matched) - matched is true if hashed data matches hash
module.exports.compare = function compare(data, hash, cb) {
var error;
if (typeof data === 'function') {
error = new Error('data and hash arguments required');
return process.nextTick(function() {
data(error);
});
}
if (typeof hash === 'function') {
error = new Error('data and hash arguments required');
return process.nextTick(function() {
hash(error);
});
}
// cb exists but is not a function
// return a rejecting promise
if (cb && typeof cb !== 'function') {
return promises.reject(new Error('cb must be a function or null to return a Promise'));
}
if (!cb) {
return promises.promise(compare, this, [data, hash]);
}
if (data == null || hash == null) {
error = new Error('data and hash arguments required');
return process.nextTick(function() {
cb(error);
});
}
if (!(typeof data === 'string' || data instanceof Buffer) || typeof hash !== 'string') {
error = new Error('data and hash must be strings');
return process.nextTick(function() {
cb(error);
});
}
return bindings.compare(data, hash, cb);
};
/// @param {String} hash extract rounds from this hash
/// @return {Number} the number of rounds used to encrypt a given hash
module.exports.getRounds = function getRounds(hash) {
if (hash == null) {
throw new Error('hash argument required');
}
if (typeof hash !== 'string') {
throw new Error('hash must be a string');
}
return bindings.get_rounds(hash);
};