-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpashword.ts
190 lines (166 loc) · 6.86 KB
/
pashword.ts
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
import { scrypt } from 'scrypt-js';
import jsSHA from 'jssha';
import { intArrToBigint } from './bigint';
import { debug } from 'debug';
const _debug = debug('pashword');
// Variables for Scrypt Hashing
const CPU_COST = 1 << 15; // 32768
const BLOCK_SIZE = 8;
const PARALLELIZATION_COST = 1;
const ALLOWED_CHARACTERS = '@#$%&*._!0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const UPPERCASE_LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const LOWERCASE_LETTERS = 'abcdefghijklmnopqrstuvwxyz';
const NUMBERS = '1234567890';
const VALID_SYMBOLS = '@#$%&*._!';
// Takes in string and returns an array of bytes
const encodeUtf8 = (str: string): Uint8Array => {
return new TextEncoder().encode(str);
};
// Removes Numbers or Symbols from Pashword
export const sanitize = (pashword: string, no_symbols?: boolean, no_numbers?: boolean): string => {
const _ = _debug.extend('sanitize');
// Initialize SHAKE256 for PRNG
/* DEBUG */ _('Initializing SHAKE256');
const prng_obj = new jsSHA('SHAKE256', 'TEXT', { encoding: 'UTF8' });
// Convert Pashword to Array of Strings
/* DEBUG */ _('Splitting pashword by character');
const pashword_array = pashword.split('');
// Generate a random number from 0 to alphabets.length-1
const generateIndex = (hashThis: string): number => {
prng_obj.update(hashThis);
// Same process as generateIndex() in generatePashword()
const prng = prng_obj.getHash('UINT8ARRAY', { outputLen: 256 });
const result = intArrToBigint(prng);
return Number(result % BigInt(ALPHABET.length));
};
// If symbols have to be removed
if (no_symbols) {
/* DEBUG */ _('no_symbols is true, removing symbols');
for (let i = 0; i < pashword_array.length; i++) {
// If character is a symbol
if (VALID_SYMBOLS.includes(pashword_array[i])) {
// Generate a random index using the (index+symbol)
// Replace the symbol with an alphabet at the generated index
pashword_array[i] = ALPHABET[generateIndex(i.toString() + pashword_array[i])];
}
}
}
// If numbers have to be removed
if (no_numbers) {
/* DEBUG */ _('no_numbers is true, removing numbers');
for (let i = 0; i < pashword_array.length; i++) {
// If character is a number
if (parseInt(pashword_array[i]) >= 0 && parseInt(pashword_array[i]) <= 9) {
// Generate a random index using the (index+number)
// Replace the number with an alphabet at the generated index
pashword_array[i] = ALPHABET[generateIndex(i.toString() + pashword_array[i])];
}
}
}
// Return the pashword array as a string
/* DEBUG */ _('Returning pashword character array as a string');
return pashword_array.join('');
};
export const generatePashword = async (
to_hash: string,
pashword_length: number,
website: string,
username: string,
): Promise<string> => {
const _ = _debug.extend('generatePashword');
/* DEBUG */ _('Initializing SHAKE256 and SHA3-512');
// INITIALIZE SHAKE256 FOR PRNG
const sha_obj = new jsSHA('SHAKE256', 'UINT8ARRAY');
// INITIALIZE SHA3-512 FOR FIRST PASS
const sha3_obj = new jsSHA('SHA3-512', 'TEXT', { encoding: 'UTF8' });
// Convert to_hash json object to its own SHA3-512 hash sum in HEX
/* DEBUG */ _('Hashing to_hash');
sha3_obj.update(to_hash);
to_hash = sha3_obj.getHash('HEX');
// Generate Scrypt Hash
/* DEBUG */ _('Generating Scrypt Hash of hashed to_hash, salting with website + username');
const scrypt_hash = await scrypt(
encodeUtf8(to_hash),
encodeUtf8(website + username),
CPU_COST,
BLOCK_SIZE,
PARALLELIZATION_COST,
32,
);
/* DEBUG */ _('Scrypt hash generated');
// Generate a random number from 0 to modulo-1
const generateIndex = (modulo: number) => {
// Feed sryptHash as seed to SHAKE256
sha_obj.update(scrypt_hash);
// PRNG is a 256 bit array generated by SHAKE256
const prng = sha_obj.getHash('UINT8ARRAY', { outputLen: 256 });
// Convert the 256 bit array to 256 bit BigInt
const result = intArrToBigint(prng);
return Number(result % BigInt(modulo));
};
// generate a random index from 0 to characterSet.length
// and return the character at that index
const pickCharacter = (char_set: string) => {
return char_set[generateIndex(char_set.length)];
};
// Generate Array: 0,1,2,3,4,5,6...pashwordLength-1
let pick_index = [];
// See https://jsben.ch/3YHpR
// Chrome/v8 seems to handle array operations quite a bit faster than
// firefox, so using Array.from() with a custom "iterable" is a good idea on
// for perfomance sake, even if negligeable.
if (globalThis?.navigator?.userAgent?.includes('Firefox')) {
/* DEBUG */ _('Firefox detected, using Array.from with length and entry modifier');
pick_index = Array.from({ length: pashword_length }, (_, i) => i);
} else {
/* DEBUG */ _('Not Firefox, using Set tmp[i] = i within forloop method');
for (let i = 0; i < pashword_length; i++) {
// Setting the index is much faster than pushing to the array, and
// because we're operating in order, it's relatively safe to do so.
pick_index[i] = i;
}
}
/* DEBUG */ _('Generating pashword from alphabet');
// generate a random index from 0 to pickIndex.length
let remove_index = generateIndex(pick_index.length);
// index1 is the element at that generated index
// and the value is between 0 and pashwordLength-1
const index1 = pick_index[remove_index];
// remove that element from the pickIndex array
// we can never pick the same element from pickIndex again
// and pickIndex.length decrements by one
pick_index.splice(remove_index, 1);
// Repeat 3 more times for index2, index3 and index4
remove_index = generateIndex(pick_index.length);
const index2 = pick_index[remove_index];
pick_index.splice(remove_index, 1);
remove_index = generateIndex(pick_index.length);
const index3 = pick_index[remove_index];
pick_index.splice(remove_index, 1);
remove_index = generateIndex(pick_index.length);
const index4 = pick_index[remove_index];
pick_index.splice(remove_index, 1);
let pashword = '';
// Build a pashword from 0 to pashwordLength-1
for (let i = 0; i < pashword_length; i++) {
if (i === index1) {
// pick character from lowercase if index1
pashword += pickCharacter(LOWERCASE_LETTERS);
} else if (i === index2) {
// pick character from uppercase if index2
pashword += pickCharacter(UPPERCASE_LETTERS);
} else if (i === index3) {
// pick character from symbols if index3
pashword += pickCharacter(VALID_SYMBOLS);
} else if (i === index4) {
// pick character from numbers if index4
pashword += pickCharacter(NUMBERS);
} else {
// pick a character from universal set for other indices
pashword += pickCharacter(ALLOWED_CHARACTERS);
}
}
/* DEBUG */ _('Returning pashword');
return pashword;
};