@lakutata/security 3.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +143 -0
  3. package/THIRD_PARTY_NOTICES.md +36 -0
  4. package/dist/cjs/exceptions/DecryptionFailedException.d.ts +13 -0
  5. package/dist/cjs/exceptions/DecryptionFailedException.js +20 -0
  6. package/dist/cjs/exceptions/InvalidSignatureException.d.ts +13 -0
  7. package/dist/cjs/exceptions/InvalidSignatureException.js +20 -0
  8. package/dist/cjs/exceptions/PasswordTooLongException.d.ts +12 -0
  9. package/dist/cjs/exceptions/PasswordTooLongException.js +19 -0
  10. package/dist/cjs/exports/Security.d.ts +19 -0
  11. package/dist/cjs/exports/Security.js +38 -0
  12. package/dist/cjs/functions/Encryption.d.ts +95 -0
  13. package/dist/cjs/functions/Encryption.js +208 -0
  14. package/dist/cjs/functions/GenerateSecret.d.ts +20 -0
  15. package/dist/cjs/functions/GenerateSecret.js +26 -0
  16. package/dist/cjs/functions/HMAC.d.ts +50 -0
  17. package/dist/cjs/functions/HMAC.js +58 -0
  18. package/dist/cjs/functions/TimingSafeEqual.d.ts +18 -0
  19. package/dist/cjs/functions/TimingSafeEqual.js +30 -0
  20. package/dist/cjs/lib/AssertKey.d.ts +5 -0
  21. package/dist/cjs/lib/AssertKey.js +11 -0
  22. package/dist/cjs/lib/bcrypt/constants.d.ts +13 -0
  23. package/dist/cjs/lib/bcrypt/constants.js +213 -0
  24. package/dist/cjs/lib/bcrypt/index.d.ts +84 -0
  25. package/dist/cjs/lib/bcrypt/index.js +658 -0
  26. package/dist/cjs/package.json +1 -0
  27. package/dist/cjs/providers/Crypt.d.ts +71 -0
  28. package/dist/cjs/providers/Crypt.js +88 -0
  29. package/dist/cjs/providers/Mac.d.ts +86 -0
  30. package/dist/cjs/providers/Mac.js +114 -0
  31. package/dist/cjs/providers/PasswordHash.d.ts +78 -0
  32. package/dist/cjs/providers/PasswordHash.js +146 -0
  33. package/dist/esm/exceptions/DecryptionFailedException.js +16 -0
  34. package/dist/esm/exceptions/InvalidSignatureException.js +16 -0
  35. package/dist/esm/exceptions/PasswordTooLongException.js +15 -0
  36. package/dist/esm/exports/Security.js +19 -0
  37. package/dist/esm/functions/Encryption.js +202 -0
  38. package/dist/esm/functions/GenerateSecret.js +23 -0
  39. package/dist/esm/functions/HMAC.js +54 -0
  40. package/dist/esm/functions/TimingSafeEqual.js +27 -0
  41. package/dist/esm/lib/AssertKey.js +8 -0
  42. package/dist/esm/lib/bcrypt/constants.js +202 -0
  43. package/dist/esm/lib/bcrypt/index.js +645 -0
  44. package/dist/esm/providers/Crypt.js +84 -0
  45. package/dist/esm/providers/Mac.js +110 -0
  46. package/dist/esm/providers/PasswordHash.js +109 -0
  47. package/dist/types/exceptions/DecryptionFailedException.d.ts +13 -0
  48. package/dist/types/exceptions/InvalidSignatureException.d.ts +13 -0
  49. package/dist/types/exceptions/PasswordTooLongException.d.ts +12 -0
  50. package/dist/types/exports/Security.d.ts +19 -0
  51. package/dist/types/functions/Encryption.d.ts +95 -0
  52. package/dist/types/functions/GenerateSecret.d.ts +20 -0
  53. package/dist/types/functions/HMAC.d.ts +50 -0
  54. package/dist/types/functions/TimingSafeEqual.d.ts +18 -0
  55. package/dist/types/lib/AssertKey.d.ts +5 -0
  56. package/dist/types/lib/bcrypt/constants.d.ts +13 -0
  57. package/dist/types/lib/bcrypt/index.d.ts +84 -0
  58. package/dist/types/providers/Crypt.d.ts +71 -0
  59. package/dist/types/providers/Mac.d.ts +86 -0
  60. package/dist/types/providers/PasswordHash.d.ts +78 -0
  61. package/package.json +45 -0
@@ -0,0 +1,645 @@
1
+ /**
2
+ * Derived from the upstream index.js (see UPSTREAM.md; BSD-3-Clause, see THIRD_PARTY_NOTICES.md)
3
+ *
4
+ * The bcrypt password hashing (Blowfish based), compatible with the other bcrypt implementations ($2a$, $2b$, $2y$).
5
+ */
6
+ import { randomBytes } from 'node:crypto';
7
+ import { BASE64_CODE, BASE64_INDEX, BCRYPT_SALT_LEN, BLOWFISH_NUM_ROUNDS, C_ORIG, GENSALT_DEFAULT_LOG2_ROUNDS, MAX_EXECUTION_TIME, P_ORIG, S_ORIG } from './constants.js';
8
+ /**
9
+ * Continues after yielding to the event loop
10
+ */
11
+ const nextTick = setImmediate;
12
+ /**
13
+ * Synchronously generates a salt.
14
+ * @param rounds Number of rounds to use, defaults to 10 if omitted (from 4 to 31)
15
+ * @returns Resulting salt
16
+ */
17
+ export function genSaltSync(rounds) {
18
+ rounds = rounds || GENSALT_DEFAULT_LOG2_ROUNDS;
19
+ if (typeof rounds !== 'number')
20
+ throw Error('Illegal arguments: ' + typeof rounds + ', undefined');
21
+ if (rounds < 4) {
22
+ rounds = 4;
23
+ }
24
+ else if (rounds > 31) {
25
+ rounds = 31;
26
+ }
27
+ const salt = [];
28
+ salt.push('$2b$');
29
+ if (rounds < 10)
30
+ salt.push('0');
31
+ salt.push(rounds.toString());
32
+ salt.push('$');
33
+ salt.push(base64Encode(randomBytes(BCRYPT_SALT_LEN), BCRYPT_SALT_LEN));
34
+ return salt.join('');
35
+ }
36
+ /**
37
+ * Asynchronously generates a salt.
38
+ * @param rounds Number of rounds to use, defaults to 10 if omitted
39
+ * @returns Resulting salt
40
+ */
41
+ export function genSalt(rounds) {
42
+ if (typeof rounds === 'undefined') {
43
+ rounds = GENSALT_DEFAULT_LOG2_ROUNDS;
44
+ }
45
+ else if (typeof rounds !== 'number') {
46
+ throw Error('illegal arguments: ' + typeof rounds);
47
+ }
48
+ const saltRounds = rounds;
49
+ return new Promise((resolve, reject) => nextTick(() => {
50
+ //Pretty thin, but salting is fast enough
51
+ try {
52
+ resolve(genSaltSync(saltRounds));
53
+ }
54
+ catch (error) {
55
+ reject(error);
56
+ }
57
+ }));
58
+ }
59
+ /**
60
+ * Synchronously generates a hash for the given password.
61
+ * @param password Password to hash
62
+ * @param salt Salt length to generate or salt to use, default to 10
63
+ * @returns Resulting hash
64
+ */
65
+ export function hashSync(password, salt = GENSALT_DEFAULT_LOG2_ROUNDS) {
66
+ if (typeof salt === 'number')
67
+ salt = genSaltSync(salt);
68
+ if (typeof password !== 'string' || typeof salt !== 'string')
69
+ throw Error('Illegal arguments: ' + typeof password + ', ' + typeof salt);
70
+ return hashPassword(password, salt);
71
+ }
72
+ /**
73
+ * Asynchronously generates a hash for the given password.
74
+ * @param password Password to hash
75
+ * @param salt Salt length to generate or salt to use
76
+ * @param progressCallback Callback successively called with the percentage of rounds completed (0.0 - 1.0), maximally
77
+ * once per `MAX_EXECUTION_TIME = 100` ms.
78
+ * @returns Resulting hash
79
+ */
80
+ export async function hash(password, salt, progressCallback) {
81
+ if (typeof password === 'string' && typeof salt === 'number')
82
+ salt = await genSalt(salt);
83
+ if (typeof password !== 'string' || typeof salt !== 'string') {
84
+ const error = Error('Illegal arguments: ' + typeof password + ', ' + typeof salt);
85
+ await new Promise((resolve) => nextTick(resolve));
86
+ throw error;
87
+ }
88
+ return await hashPasswordAsync(password, salt, progressCallback);
89
+ }
90
+ /**
91
+ * Compares two strings of the same length in constant time.
92
+ * @param known Must be of the correct length
93
+ * @param unknown Must be the same length as `known`
94
+ */
95
+ function safeStringCompare(known, unknown) {
96
+ let diff = known.length ^ unknown.length;
97
+ for (let i = 0; i < known.length; ++i) {
98
+ diff |= known.charCodeAt(i) ^ unknown.charCodeAt(i);
99
+ }
100
+ return diff === 0;
101
+ }
102
+ /**
103
+ * Synchronously tests a password against a hash.
104
+ * @param password Password to compare
105
+ * @param hash Hash to test against
106
+ * @returns true if matching, otherwise false
107
+ */
108
+ export function compareSync(password, hash) {
109
+ if (typeof password !== 'string' || typeof hash !== 'string')
110
+ throw Error('Illegal arguments: ' + typeof password + ', ' + typeof hash);
111
+ if (hash.length !== 60)
112
+ return false;
113
+ return safeStringCompare(hashSync(password, hash.substring(0, hash.length - 31)), hash);
114
+ }
115
+ /**
116
+ * Asynchronously tests a password against a hash.
117
+ * @param password Password to compare
118
+ * @param hashValue Hash to test against
119
+ * @param progressCallback Callback successively called with the percentage of rounds completed (0.0 - 1.0), maximally
120
+ * once per `MAX_EXECUTION_TIME = 100` ms.
121
+ * @returns true if matching, otherwise false
122
+ */
123
+ export async function compare(password, hashValue, progressCallback) {
124
+ if (typeof password !== 'string' || typeof hashValue !== 'string') {
125
+ const error = Error('Illegal arguments: ' + typeof password + ', ' + typeof hashValue);
126
+ await new Promise((resolve) => nextTick(resolve));
127
+ throw error;
128
+ }
129
+ if (hashValue.length !== 60) {
130
+ await new Promise((resolve) => nextTick(resolve));
131
+ return false;
132
+ }
133
+ return safeStringCompare(await hash(password, hashValue.substring(0, 29), progressCallback), hashValue);
134
+ }
135
+ /**
136
+ * Gets the number of rounds used to encrypt the specified hash.
137
+ * @param hash Hash to extract the used number of rounds from
138
+ * @returns Number of rounds used
139
+ */
140
+ export function getRounds(hash) {
141
+ if (typeof hash !== 'string')
142
+ throw Error('Illegal arguments: ' + typeof hash);
143
+ return parseInt(hash.split('$')[2], 10);
144
+ }
145
+ /**
146
+ * Gets the salt portion from a hash. Does not validate the hash.
147
+ * @param hash Hash to extract the salt from
148
+ * @returns Extracted salt part
149
+ */
150
+ export function getSalt(hash) {
151
+ if (typeof hash !== 'string')
152
+ throw Error('Illegal arguments: ' + typeof hash);
153
+ if (hash.length !== 60)
154
+ throw Error('Illegal hash length: ' + hash.length + ' != 60');
155
+ return hash.substring(0, 29);
156
+ }
157
+ /**
158
+ * Tests if a password will be truncated when hashed, that is its length is greater than 72 bytes when converted to
159
+ * UTF-8.
160
+ * @param password The password to test
161
+ * @returns `true` if truncated, otherwise `false`
162
+ */
163
+ export function truncates(password) {
164
+ if (typeof password !== 'string')
165
+ throw Error('Illegal arguments: ' + typeof password);
166
+ return utf8Length(password) > 72;
167
+ }
168
+ /**
169
+ * Calculates the byte length of a string encoded as UTF8.
170
+ * @param string
171
+ */
172
+ function utf8Length(string) {
173
+ let length = 0;
174
+ for (let i = 0; i < string.length; ++i) {
175
+ const c = string.charCodeAt(i);
176
+ if (c < 128) {
177
+ length += 1;
178
+ }
179
+ else if (c < 2048) {
180
+ length += 2;
181
+ }
182
+ else if ((c & 0xfc00) === 0xd800 && (string.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {
183
+ ++i;
184
+ length += 4;
185
+ }
186
+ else {
187
+ length += 3;
188
+ }
189
+ }
190
+ return length;
191
+ }
192
+ /**
193
+ * Converts a string to an array of UTF8 bytes.
194
+ * @param string
195
+ */
196
+ function utf8Array(string) {
197
+ let offset = 0;
198
+ const buffer = new Array(utf8Length(string));
199
+ for (let i = 0, k = string.length; i < k; ++i) {
200
+ let c1 = string.charCodeAt(i);
201
+ let c2;
202
+ if (c1 < 128) {
203
+ buffer[offset++] = c1;
204
+ }
205
+ else if (c1 < 2048) {
206
+ buffer[offset++] = (c1 >> 6) | 192;
207
+ buffer[offset++] = (c1 & 63) | 128;
208
+ }
209
+ else if ((c1 & 0xfc00) === 0xd800 && ((c2 = string.charCodeAt(i + 1)) & 0xfc00) === 0xdc00) {
210
+ c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff);
211
+ ++i;
212
+ buffer[offset++] = (c1 >> 18) | 240;
213
+ buffer[offset++] = ((c1 >> 12) & 63) | 128;
214
+ buffer[offset++] = ((c1 >> 6) & 63) | 128;
215
+ buffer[offset++] = (c1 & 63) | 128;
216
+ }
217
+ else {
218
+ buffer[offset++] = (c1 >> 12) | 224;
219
+ buffer[offset++] = ((c1 >> 6) & 63) | 128;
220
+ buffer[offset++] = (c1 & 63) | 128;
221
+ }
222
+ }
223
+ return buffer;
224
+ }
225
+ /**
226
+ * Encodes a byte array to base64 with up to len bytes of input, using the bcrypt alphabet.
227
+ * @param b Byte array
228
+ * @param len Maximum input length
229
+ */
230
+ function base64Encode(b, len) {
231
+ let off = 0;
232
+ const rs = [];
233
+ if (len <= 0 || len > b.length)
234
+ throw Error('Illegal len: ' + len);
235
+ while (off < len) {
236
+ let c1 = b[off++] & 0xff;
237
+ rs.push(BASE64_CODE[(c1 >> 2) & 0x3f]);
238
+ c1 = (c1 & 0x03) << 4;
239
+ if (off >= len) {
240
+ rs.push(BASE64_CODE[c1 & 0x3f]);
241
+ break;
242
+ }
243
+ let c2 = b[off++] & 0xff;
244
+ c1 |= (c2 >> 4) & 0x0f;
245
+ rs.push(BASE64_CODE[c1 & 0x3f]);
246
+ c1 = (c2 & 0x0f) << 2;
247
+ if (off >= len) {
248
+ rs.push(BASE64_CODE[c1 & 0x3f]);
249
+ break;
250
+ }
251
+ c2 = b[off++] & 0xff;
252
+ c1 |= (c2 >> 6) & 0x03;
253
+ rs.push(BASE64_CODE[c1 & 0x3f]);
254
+ rs.push(BASE64_CODE[c2 & 0x3f]);
255
+ }
256
+ return rs.join('');
257
+ }
258
+ /**
259
+ * Decodes a base64 encoded string (bcrypt alphabet) to up to len bytes of output.
260
+ * @param s String to decode
261
+ * @param len Maximum output length
262
+ */
263
+ function base64Decode(s, len) {
264
+ let off = 0;
265
+ const slen = s.length;
266
+ let olen = 0;
267
+ const rs = [];
268
+ if (len <= 0)
269
+ throw Error('Illegal len: ' + len);
270
+ while (off < slen - 1 && olen < len) {
271
+ let code = s.charCodeAt(off++);
272
+ const c1 = code < BASE64_INDEX.length ? BASE64_INDEX[code] : -1;
273
+ code = s.charCodeAt(off++);
274
+ const c2 = code < BASE64_INDEX.length ? BASE64_INDEX[code] : -1;
275
+ if (c1 == -1 || c2 == -1)
276
+ break;
277
+ let o = (c1 << 2) >>> 0;
278
+ o |= (c2 & 0x30) >> 4;
279
+ rs.push(String.fromCharCode(o));
280
+ if (++olen >= len || off >= slen)
281
+ break;
282
+ code = s.charCodeAt(off++);
283
+ const c3 = code < BASE64_INDEX.length ? BASE64_INDEX[code] : -1;
284
+ if (c3 == -1)
285
+ break;
286
+ o = ((c2 & 0x0f) << 4) >>> 0;
287
+ o |= (c3 & 0x3c) >> 2;
288
+ rs.push(String.fromCharCode(o));
289
+ if (++olen >= len || off >= slen)
290
+ break;
291
+ code = s.charCodeAt(off++);
292
+ const c4 = code < BASE64_INDEX.length ? BASE64_INDEX[code] : -1;
293
+ o = ((c3 & 0x03) << 6) >>> 0;
294
+ o |= c4;
295
+ rs.push(String.fromCharCode(o));
296
+ ++olen;
297
+ }
298
+ const res = [];
299
+ for (off = 0; off < olen; off++)
300
+ res.push(rs[off].charCodeAt(0));
301
+ return res;
302
+ }
303
+ /**
304
+ * Blowfish encryption of the pair of words at off (the bottleneck: the loop is unrolled)
305
+ * @param lr
306
+ * @param off
307
+ * @param P
308
+ * @param S
309
+ */
310
+ function encipher(lr, off, P, S) {
311
+ let n;
312
+ let l = lr[off];
313
+ let r = lr[off + 1];
314
+ l ^= P[0];
315
+ //Iteration 0
316
+ n = S[l >>> 24];
317
+ n += S[0x100 | ((l >> 16) & 0xff)];
318
+ n ^= S[0x200 | ((l >> 8) & 0xff)];
319
+ n += S[0x300 | (l & 0xff)];
320
+ r ^= n ^ P[1];
321
+ n = S[r >>> 24];
322
+ n += S[0x100 | ((r >> 16) & 0xff)];
323
+ n ^= S[0x200 | ((r >> 8) & 0xff)];
324
+ n += S[0x300 | (r & 0xff)];
325
+ l ^= n ^ P[2];
326
+ //Iteration 1
327
+ n = S[l >>> 24];
328
+ n += S[0x100 | ((l >> 16) & 0xff)];
329
+ n ^= S[0x200 | ((l >> 8) & 0xff)];
330
+ n += S[0x300 | (l & 0xff)];
331
+ r ^= n ^ P[3];
332
+ n = S[r >>> 24];
333
+ n += S[0x100 | ((r >> 16) & 0xff)];
334
+ n ^= S[0x200 | ((r >> 8) & 0xff)];
335
+ n += S[0x300 | (r & 0xff)];
336
+ l ^= n ^ P[4];
337
+ //Iteration 2
338
+ n = S[l >>> 24];
339
+ n += S[0x100 | ((l >> 16) & 0xff)];
340
+ n ^= S[0x200 | ((l >> 8) & 0xff)];
341
+ n += S[0x300 | (l & 0xff)];
342
+ r ^= n ^ P[5];
343
+ n = S[r >>> 24];
344
+ n += S[0x100 | ((r >> 16) & 0xff)];
345
+ n ^= S[0x200 | ((r >> 8) & 0xff)];
346
+ n += S[0x300 | (r & 0xff)];
347
+ l ^= n ^ P[6];
348
+ //Iteration 3
349
+ n = S[l >>> 24];
350
+ n += S[0x100 | ((l >> 16) & 0xff)];
351
+ n ^= S[0x200 | ((l >> 8) & 0xff)];
352
+ n += S[0x300 | (l & 0xff)];
353
+ r ^= n ^ P[7];
354
+ n = S[r >>> 24];
355
+ n += S[0x100 | ((r >> 16) & 0xff)];
356
+ n ^= S[0x200 | ((r >> 8) & 0xff)];
357
+ n += S[0x300 | (r & 0xff)];
358
+ l ^= n ^ P[8];
359
+ //Iteration 4
360
+ n = S[l >>> 24];
361
+ n += S[0x100 | ((l >> 16) & 0xff)];
362
+ n ^= S[0x200 | ((l >> 8) & 0xff)];
363
+ n += S[0x300 | (l & 0xff)];
364
+ r ^= n ^ P[9];
365
+ n = S[r >>> 24];
366
+ n += S[0x100 | ((r >> 16) & 0xff)];
367
+ n ^= S[0x200 | ((r >> 8) & 0xff)];
368
+ n += S[0x300 | (r & 0xff)];
369
+ l ^= n ^ P[10];
370
+ //Iteration 5
371
+ n = S[l >>> 24];
372
+ n += S[0x100 | ((l >> 16) & 0xff)];
373
+ n ^= S[0x200 | ((l >> 8) & 0xff)];
374
+ n += S[0x300 | (l & 0xff)];
375
+ r ^= n ^ P[11];
376
+ n = S[r >>> 24];
377
+ n += S[0x100 | ((r >> 16) & 0xff)];
378
+ n ^= S[0x200 | ((r >> 8) & 0xff)];
379
+ n += S[0x300 | (r & 0xff)];
380
+ l ^= n ^ P[12];
381
+ //Iteration 6
382
+ n = S[l >>> 24];
383
+ n += S[0x100 | ((l >> 16) & 0xff)];
384
+ n ^= S[0x200 | ((l >> 8) & 0xff)];
385
+ n += S[0x300 | (l & 0xff)];
386
+ r ^= n ^ P[13];
387
+ n = S[r >>> 24];
388
+ n += S[0x100 | ((r >> 16) & 0xff)];
389
+ n ^= S[0x200 | ((r >> 8) & 0xff)];
390
+ n += S[0x300 | (r & 0xff)];
391
+ l ^= n ^ P[14];
392
+ //Iteration 7
393
+ n = S[l >>> 24];
394
+ n += S[0x100 | ((l >> 16) & 0xff)];
395
+ n ^= S[0x200 | ((l >> 8) & 0xff)];
396
+ n += S[0x300 | (l & 0xff)];
397
+ r ^= n ^ P[15];
398
+ n = S[r >>> 24];
399
+ n += S[0x100 | ((r >> 16) & 0xff)];
400
+ n ^= S[0x200 | ((r >> 8) & 0xff)];
401
+ n += S[0x300 | (r & 0xff)];
402
+ l ^= n ^ P[16];
403
+ lr[off] = r ^ P[BLOWFISH_NUM_ROUNDS + 1];
404
+ lr[off + 1] = l;
405
+ return lr;
406
+ }
407
+ /**
408
+ * The next word of a cyclic byte stream
409
+ * @param data
410
+ * @param offp
411
+ */
412
+ function streamToWord(data, offp) {
413
+ let word = 0;
414
+ for (let i = 0; i < 4; ++i) {
415
+ word = (word << 8) | (data[offp] & 0xff);
416
+ offp = (offp + 1) % data.length;
417
+ }
418
+ return { key: word, offp };
419
+ }
420
+ /**
421
+ * Blowfish key schedule
422
+ * @param key
423
+ * @param P
424
+ * @param S
425
+ */
426
+ function blowfishKey(key, P, S) {
427
+ let offset = 0;
428
+ let lr = [0, 0];
429
+ const plen = P.length;
430
+ const slen = S.length;
431
+ for (let i = 0; i < plen; i++) {
432
+ const sw = streamToWord(key, offset);
433
+ offset = sw.offp;
434
+ P[i] = P[i] ^ sw.key;
435
+ }
436
+ for (let i = 0; i < plen; i += 2) {
437
+ lr = encipher(lr, 0, P, S);
438
+ P[i] = lr[0];
439
+ P[i + 1] = lr[1];
440
+ }
441
+ for (let i = 0; i < slen; i += 2) {
442
+ lr = encipher(lr, 0, P, S);
443
+ S[i] = lr[0];
444
+ S[i + 1] = lr[1];
445
+ }
446
+ }
447
+ /**
448
+ * Expensive key schedule Blowfish.
449
+ * @param data
450
+ * @param key
451
+ * @param P
452
+ * @param S
453
+ */
454
+ function expensiveKey(data, key, P, S) {
455
+ let offp = 0;
456
+ let lr = [0, 0];
457
+ const plen = P.length;
458
+ const slen = S.length;
459
+ let sw;
460
+ for (let i = 0; i < plen; i++) {
461
+ sw = streamToWord(key, offp);
462
+ offp = sw.offp;
463
+ P[i] = P[i] ^ sw.key;
464
+ }
465
+ offp = 0;
466
+ for (let i = 0; i < plen; i += 2) {
467
+ sw = streamToWord(data, offp);
468
+ offp = sw.offp;
469
+ lr[0] ^= sw.key;
470
+ sw = streamToWord(data, offp);
471
+ offp = sw.offp;
472
+ lr[1] ^= sw.key;
473
+ lr = encipher(lr, 0, P, S);
474
+ P[i] = lr[0];
475
+ P[i + 1] = lr[1];
476
+ }
477
+ for (let i = 0; i < slen; i += 2) {
478
+ sw = streamToWord(data, offp);
479
+ offp = sw.offp;
480
+ lr[0] ^= sw.key;
481
+ sw = streamToWord(data, offp);
482
+ offp = sw.offp;
483
+ lr[1] ^= sw.key;
484
+ lr = encipher(lr, 0, P, S);
485
+ S[i] = lr[0];
486
+ S[i + 1] = lr[1];
487
+ }
488
+ }
489
+ /**
490
+ * Starts crypting bytes (validates the rounds and the salt)
491
+ * @param b Bytes to crypt
492
+ * @param salt Salt bytes to use
493
+ * @param rounds Number of rounds (log2)
494
+ */
495
+ function startCrypt(b, salt, rounds) {
496
+ if (rounds < 4 || rounds > 31)
497
+ throw Error('Illegal number of rounds (4-31): ' + rounds);
498
+ if (salt.length !== BCRYPT_SALT_LEN)
499
+ throw Error('Illegal salt length: ' + salt.length + ' != ' + BCRYPT_SALT_LEN);
500
+ const state = { b, salt, rounds: (1 << rounds) >>> 0, P: new Int32Array(P_ORIG), S: new Int32Array(S_ORIG), i: 0 };
501
+ expensiveKey(salt, b, state.P, state.S);
502
+ return state;
503
+ }
504
+ /**
505
+ * Runs rounds of a crypt, for at most a time (Infinity: all the rounds)
506
+ * @param state
507
+ * @param maxTime
508
+ */
509
+ function runRounds(state, maxTime) {
510
+ const start = Date.now();
511
+ while (state.i < state.rounds) {
512
+ state.i = state.i + 1;
513
+ blowfishKey(state.b, state.P, state.S);
514
+ blowfishKey(state.salt, state.P, state.S);
515
+ if (Date.now() - start > maxTime)
516
+ break;
517
+ }
518
+ }
519
+ /**
520
+ * Finishes a crypt: the bytes of the encrypted magic value
521
+ * @param state
522
+ */
523
+ function finishCrypt(state) {
524
+ const cdata = C_ORIG.slice();
525
+ const clen = cdata.length;
526
+ for (let i = 0; i < 64; i++) {
527
+ for (let j = 0; j < clen >> 1; j++)
528
+ encipher(cdata, j << 1, state.P, state.S);
529
+ }
530
+ const ret = [];
531
+ for (let i = 0; i < clen; i++) {
532
+ ret.push(((cdata[i] >> 24) & 0xff) >>> 0);
533
+ ret.push(((cdata[i] >> 16) & 0xff) >>> 0);
534
+ ret.push(((cdata[i] >> 8) & 0xff) >>> 0);
535
+ ret.push((cdata[i] & 0xff) >>> 0);
536
+ }
537
+ return ret;
538
+ }
539
+ /**
540
+ * Parses a salt, and prepares the password bytes
541
+ * @param password
542
+ * @param salt
543
+ */
544
+ function parseSalt(password, salt) {
545
+ if (typeof password !== 'string' || typeof salt !== 'string')
546
+ throw Error('Invalid string / salt: Not a string');
547
+ //Validate the salt
548
+ let minor;
549
+ let offset;
550
+ if (salt.charAt(0) !== '$' || salt.charAt(1) !== '2')
551
+ throw Error('Invalid salt version: ' + salt.substring(0, 2));
552
+ if (salt.charAt(2) === '$') {
553
+ minor = String.fromCharCode(0);
554
+ offset = 3;
555
+ }
556
+ else {
557
+ minor = salt.charAt(2);
558
+ if ((minor !== 'a' && minor !== 'b' && minor !== 'y') || salt.charAt(3) !== '$')
559
+ throw Error('Invalid salt revision: ' + salt.substring(2, 4));
560
+ offset = 4;
561
+ }
562
+ //Extract number of rounds
563
+ if (salt.charAt(offset + 2) > '$')
564
+ throw Error('Missing salt rounds');
565
+ const r1 = parseInt(salt.substring(offset, offset + 1), 10) * 10;
566
+ const r2 = parseInt(salt.substring(offset + 1, offset + 2), 10);
567
+ const rounds = r1 + r2;
568
+ const realSalt = salt.substring(offset + 3, offset + 25);
569
+ password += minor >= 'a' ? '\x00' : '';
570
+ return { minor, rounds, passwordBytes: utf8Array(password), saltBytes: base64Decode(realSalt, BCRYPT_SALT_LEN) };
571
+ }
572
+ /**
573
+ * The hash string of the crypted bytes
574
+ * @param parts
575
+ * @param bytes
576
+ */
577
+ function formatHash(parts, bytes) {
578
+ const res = [];
579
+ res.push('$2');
580
+ if (parts.minor >= 'a')
581
+ res.push(parts.minor);
582
+ res.push('$');
583
+ if (parts.rounds < 10)
584
+ res.push('0');
585
+ res.push(parts.rounds.toString());
586
+ res.push('$');
587
+ res.push(base64Encode(parts.saltBytes, parts.saltBytes.length));
588
+ res.push(base64Encode(bytes, C_ORIG.length * 4 - 1));
589
+ return res.join('');
590
+ }
591
+ /**
592
+ * Hashes a password synchronously.
593
+ * @param password Password to hash
594
+ * @param salt Salt to use
595
+ */
596
+ function hashPassword(password, salt) {
597
+ const parts = parseSalt(password, salt);
598
+ const state = startCrypt(parts.passwordBytes, parts.saltBytes, parts.rounds);
599
+ runRounds(state, Infinity);
600
+ return formatHash(parts, finishCrypt(state));
601
+ }
602
+ /**
603
+ * Hashes a password asynchronously: the rounds run at most 100 ms at a time, yielding to the event loop in between.
604
+ * @param password Password to hash
605
+ * @param salt Salt to use
606
+ * @param progressCallback
607
+ */
608
+ async function hashPasswordAsync(password, salt, progressCallback) {
609
+ let parts;
610
+ let state;
611
+ try {
612
+ parts = parseSalt(password, salt);
613
+ state = startCrypt(parts.passwordBytes, parts.saltBytes, parts.rounds);
614
+ }
615
+ catch (error) {
616
+ //The errors are reported asynchronously
617
+ await new Promise((resolve) => nextTick(resolve));
618
+ throw error;
619
+ }
620
+ for (;;) {
621
+ if (progressCallback)
622
+ progressCallback(state.i / state.rounds);
623
+ if (state.i >= state.rounds)
624
+ break;
625
+ runRounds(state, MAX_EXECUTION_TIME);
626
+ await new Promise((resolve) => nextTick(resolve));
627
+ }
628
+ return formatHash(parts, finishCrypt(state));
629
+ }
630
+ /**
631
+ * Encodes a byte array to base64 with up to len bytes of input, using the custom bcrypt alphabet.
632
+ * @param bytes Byte array
633
+ * @param length Maximum input length
634
+ */
635
+ export function encodeBase64(bytes, length) {
636
+ return base64Encode(bytes, length);
637
+ }
638
+ /**
639
+ * Decodes a base64 encoded string to up to len bytes of output, using the custom bcrypt alphabet.
640
+ * @param string String to decode
641
+ * @param length Maximum output length
642
+ */
643
+ export function decodeBase64(string, length) {
644
+ return base64Decode(string, length);
645
+ }