UNPKG

11.4 kBJavaScriptView Raw
1/**
2 * bcrypt namespace.
3 * @type {Object.<string,*>}
4 */
5var bcrypt = {};
6
7/**
8 * The random implementation to use as a fallback.
9 * @type {?function(number):!Array.<number>}
10 * @inner
11 */
12var randomFallback = null;
13
14/**
15 * Generates cryptographically secure random bytes.
16 * @function
17 * @param {number} len Bytes length
18 * @returns {!Array.<number>} Random bytes
19 * @throws {Error} If no random implementation is available
20 * @inner
21 */
22function random(len) {
23 /* node */ if (typeof module !== 'undefined' && module && module['exports'])
24 try {
25 return require("crypto")['randomBytes'](len);
26 } catch (e) {}
27 /* WCA */ try {
28 var a; (self['crypto']||self['msCrypto'])['getRandomValues'](a = new Uint32Array(len));
29 return Array.prototype.slice.call(a);
30 } catch (e) {}
31 /* fallback */ if (!randomFallback)
32 throw Error("Neither WebCryptoAPI nor a crypto module is available. Use bcrypt.setRandomFallback to set an alternative");
33 return randomFallback(len);
34}
35
36// Test if any secure randomness source is available
37var randomAvailable = false;
38try {
39 random(1);
40 randomAvailable = true;
41} catch (e) {}
42
43// Default fallback, if any
44randomFallback = /*? if (ISAAC) { */function(len) {
45 for (var a=[], i=0; i<len; ++i)
46 a[i] = ((0.5 + isaac() * 2.3283064365386963e-10) * 256) | 0;
47 return a;
48};/*? } else { */null;/*? }*/
49
50/**
51 * Sets the pseudo random number generator to use as a fallback if neither node's `crypto` module nor the Web Crypto
52 * API is available. Please note: It is highly important that the PRNG used is cryptographically secure and that it
53 * is seeded properly!
54 * @param {?function(number):!Array.<number>} random Function taking the number of bytes to generate as its
55 * sole argument, returning the corresponding array of cryptographically secure random byte values.
56 * @see http://nodejs.org/api/crypto.html
57 * @see http://www.w3.org/TR/WebCryptoAPI/
58 */
59bcrypt.setRandomFallback = function(random) {
60 randomFallback = random;
61};
62
63/**
64 * Synchronously generates a salt.
65 * @param {number=} rounds Number of rounds to use, defaults to 10 if omitted
66 * @param {number=} seed_length Not supported.
67 * @returns {string} Resulting salt
68 * @throws {Error} If a random fallback is required but not set
69 * @expose
70 */
71bcrypt.genSaltSync = function(rounds, seed_length) {
72 rounds = rounds || GENSALT_DEFAULT_LOG2_ROUNDS;
73 if (typeof rounds !== 'number')
74 throw Error("Illegal arguments: "+(typeof rounds)+", "+(typeof seed_length));
75 if (rounds < 4)
76 rounds = 4;
77 else if (rounds > 31)
78 rounds = 31;
79 var salt = [];
80 salt.push("$2a$");
81 if (rounds < 10)
82 salt.push("0");
83 salt.push(rounds.toString());
84 salt.push('$');
85 salt.push(base64_encode(random(BCRYPT_SALT_LEN), BCRYPT_SALT_LEN)); // May throw
86 return salt.join('');
87};
88
89/**
90 * Asynchronously generates a salt.
91 * @param {(number|function(Error, string=))=} rounds Number of rounds to use, defaults to 10 if omitted
92 * @param {(number|function(Error, string=))=} seed_length Not supported.
93 * @param {function(Error, string=)=} callback Callback receiving the error, if any, and the resulting salt
94 * @returns {!Promise} If `callback` has been omitted
95 * @throws {Error} If `callback` is present but not a function
96 * @expose
97 */
98bcrypt.genSalt = function(rounds, seed_length, callback) {
99 if (typeof seed_length === 'function')
100 callback = seed_length,
101 seed_length = undefined; // Not supported.
102 if (typeof rounds === 'function')
103 callback = rounds,
104 rounds = undefined;
105 if (typeof rounds === 'undefined')
106 rounds = GENSALT_DEFAULT_LOG2_ROUNDS;
107 else if (typeof rounds !== 'number')
108 throw Error("illegal arguments: "+(typeof rounds));
109
110 function _async(callback) {
111 nextTick(function() { // Pretty thin, but salting is fast enough
112 try {
113 callback(null, bcrypt.genSaltSync(rounds));
114 } catch (err) {
115 callback(err);
116 }
117 });
118 }
119
120 if (callback) {
121 if (typeof callback !== 'function')
122 throw Error("Illegal callback: "+typeof(callback));
123 _async(callback);
124 } else
125 return new Promise(function(resolve, reject) {
126 _async(function(err, res) {
127 if (err) {
128 reject(err);
129 return;
130 }
131 resolve(res);
132 });
133 });
134};
135
136/**
137 * Synchronously generates a hash for the given string.
138 * @param {string} s String to hash
139 * @param {(number|string)=} salt Salt length to generate or salt to use, default to 10
140 * @returns {string} Resulting hash
141 * @expose
142 */
143bcrypt.hashSync = function(s, salt) {
144 if (typeof salt === 'undefined')
145 salt = GENSALT_DEFAULT_LOG2_ROUNDS;
146 if (typeof salt === 'number')
147 salt = bcrypt.genSaltSync(salt);
148 if (typeof s !== 'string' || typeof salt !== 'string')
149 throw Error("Illegal arguments: "+(typeof s)+', '+(typeof salt));
150 return _hash(s, salt);
151};
152
153/**
154 * Asynchronously generates a hash for the given string.
155 * @param {string} s String to hash
156 * @param {number|string} salt Salt length to generate or salt to use
157 * @param {function(Error, string=)=} callback Callback receiving the error, if any, and the resulting hash
158 * @param {function(number)=} progressCallback Callback successively called with the percentage of rounds completed
159 * (0.0 - 1.0), maximally once per `MAX_EXECUTION_TIME = 100` ms.
160 * @returns {!Promise} If `callback` has been omitted
161 * @throws {Error} If `callback` is present but not a function
162 * @expose
163 */
164bcrypt.hash = function(s, salt, callback, progressCallback) {
165
166 function _async(callback) {
167 if (typeof s === 'string' && typeof salt === 'number')
168 bcrypt.genSalt(salt, function(err, salt) {
169 _hash(s, salt, callback, progressCallback);
170 });
171 else if (typeof s === 'string' && typeof salt === 'string')
172 _hash(s, salt, callback, progressCallback);
173 else
174 nextTick(callback.bind(this, Error("Illegal arguments: "+(typeof s)+', '+(typeof salt))));
175 }
176
177 if (callback) {
178 if (typeof callback !== 'function')
179 throw Error("Illegal callback: "+typeof(callback));
180 _async(callback);
181 } else
182 return new Promise(function(resolve, reject) {
183 _async(function(err, res) {
184 if (err) {
185 reject(err);
186 return;
187 }
188 resolve(res);
189 });
190 });
191};
192
193/**
194 * Compares two strings of the same length in constant time.
195 * @param {string} known Must be of the correct length
196 * @param {string} unknown Must be the same length as `known`
197 * @returns {boolean}
198 * @inner
199 */
200function safeStringCompare(known, unknown) {
201 var right = 0,
202 wrong = 0;
203 for (var i=0, k=known.length; i<k; ++i) {
204 if (known.charCodeAt(i) === unknown.charCodeAt(i))
205 ++right;
206 else
207 ++wrong;
208 }
209 // Prevent removal of unused variables (never true, actually)
210 if (right < 0)
211 return false;
212 return wrong === 0;
213}
214
215/**
216 * Synchronously tests a string against a hash.
217 * @param {string} s String to compare
218 * @param {string} hash Hash to test against
219 * @returns {boolean} true if matching, otherwise false
220 * @throws {Error} If an argument is illegal
221 * @expose
222 */
223bcrypt.compareSync = function(s, hash) {
224 if (typeof s !== "string" || typeof hash !== "string")
225 throw Error("Illegal arguments: "+(typeof s)+', '+(typeof hash));
226 if (hash.length !== 60)
227 return false;
228 return safeStringCompare(bcrypt.hashSync(s, hash.substr(0, hash.length-31)), hash);
229};
230
231/**
232 * Asynchronously compares the given data against the given hash.
233 * @param {string} s Data to compare
234 * @param {string} hash Data to be compared to
235 * @param {function(Error, boolean)=} callback Callback receiving the error, if any, otherwise the result
236 * @param {function(number)=} progressCallback Callback successively called with the percentage of rounds completed
237 * (0.0 - 1.0), maximally once per `MAX_EXECUTION_TIME = 100` ms.
238 * @returns {!Promise} If `callback` has been omitted
239 * @throws {Error} If `callback` is present but not a function
240 * @expose
241 */
242bcrypt.compare = function(s, hash, callback, progressCallback) {
243
244 function _async(callback) {
245 if (typeof s !== "string" || typeof hash !== "string") {
246 nextTick(callback.bind(this, Error("Illegal arguments: "+(typeof s)+', '+(typeof hash))));
247 return;
248 }
249 if (hash.length !== 60) {
250 nextTick(callback.bind(this, null, false));
251 return;
252 }
253 bcrypt.hash(s, hash.substr(0, 29), function(err, comp) {
254 if (err)
255 callback(err);
256 else
257 callback(null, safeStringCompare(comp, hash));
258 }, progressCallback);
259 }
260
261 if (callback) {
262 if (typeof callback !== 'function')
263 throw Error("Illegal callback: "+typeof(callback));
264 _async(callback);
265 } else
266 return new Promise(function(resolve, reject) {
267 _async(function(err, res) {
268 if (err) {
269 reject(err);
270 return;
271 }
272 resolve(res);
273 });
274 });
275};
276
277/**
278 * Gets the number of rounds used to encrypt the specified hash.
279 * @param {string} hash Hash to extract the used number of rounds from
280 * @returns {number} Number of rounds used
281 * @throws {Error} If `hash` is not a string
282 * @expose
283 */
284bcrypt.getRounds = function(hash) {
285 if (typeof hash !== "string")
286 throw Error("Illegal arguments: "+(typeof hash));
287 return parseInt(hash.split("$")[2], 10);
288};
289
290/**
291 * Gets the salt portion from a hash. Does not validate the hash.
292 * @param {string} hash Hash to extract the salt from
293 * @returns {string} Extracted salt part
294 * @throws {Error} If `hash` is not a string or otherwise invalid
295 * @expose
296 */
297bcrypt.getSalt = function(hash) {
298 if (typeof hash !== 'string')
299 throw Error("Illegal arguments: "+(typeof hash));
300 if (hash.length !== 60)
301 throw Error("Illegal hash length: "+hash.length+" != 60");
302 return hash.substring(0, 29);
303};
304
305//? include("bcrypt/util.js");
306
307//? include("bcrypt/impl.js");
308
309/**
310 * Encodes a byte array to base64 with up to len bytes of input, using the custom bcrypt alphabet.
311 * @function
312 * @param {!Array.<number>} b Byte array
313 * @param {number} len Maximum input length
314 * @returns {string}
315 * @expose
316 */
317bcrypt.encodeBase64 = base64_encode;
318
319/**
320 * Decodes a base64 encoded string to up to len bytes of output, using the custom bcrypt alphabet.
321 * @function
322 * @param {string} s String to decode
323 * @param {number} len Maximum output length
324 * @returns {!Array.<number>}
325 * @expose
326 */
327bcrypt.decodeBase64 = base64_decode;