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