Skip to content

Instantly share code, notes, and snippets.

@Ravenstine
Created March 15, 2020 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Ravenstine/8053651dd2aeacee9973b9f93a892311 to your computer and use it in GitHub Desktop.
Save Ravenstine/8053651dd2aeacee9973b9f93a892311 to your computer and use it in GitHub Desktop.
Master Key - A stateless password manager
import scrypt from 'scrypt-js';
const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01233456789~!@#$%^&*_-+=`|(){}[]:;"\'<>,.?/';
/**
* Checks for presence of lowercase letters, uppercase letters, digits, and special characters
*/
const VALIDATION_REGEX = /^(?=.*[a-z]+)(?=.*[A-Z]+)(?=.*[0-9]+)(?=.*[\W]+).*$/;
/**
* Takes an array of bytes and converts it to a string of substituted characters.
*
* @param {Uint8Array|Array} array - An array of byte values
* @param {String} alphabet - The alphabet to use for substitution
*/
function substitute(array, alphabet) {
const limit = alphabet.length - 1;
return Array.from(array).map(int => alphabet[int % limit]).join('');
}
/**
* Generates a unique password based on the parameters.
* It does this by using Scrypt, with the private info(i.e. password, nonce, etc)
* being the main input, and the public info(domain, user id) as the salt.
*
* @param {String} domain
* @param {String} userId
* @param {String} password
* @param {Number} nonce
* @param {Number} numCharacters
* @param {String} alphabet
* @param {RegExp} validationRegex
* @yields {Promise<String|null>}
*/
function* generatePassword(domain, userId, password, nonce, numCharacters, alphabet, validationRegex) {
let attempt = 0;
while (true) {
yield new Promise(async resolve => {
const digest = await scrypt.scrypt(Buffer.from(`${password}:${nonce}:${attempt}`), Buffer.from(`${domain}:${userId}`), 16384, 8, 1, numCharacters);
const output = substitute(digest, alphabet);
if (validationRegex.test(output)) return resolve(output);
attempt++;
resolve(null);
});
}
}
/**
* @param {String} params.domain
* @param {String} params.userId
* @param {String} params.password
* @param {Number=1} params.nonce
* @param {String} params.alphabet
* @param {Number=8} params.numCharacters
* @param {RegExp} params.validationRegex
* @returns {Promise<String>}
*/
export default async function masterKey({
domain,
userId,
password,
nonce=1,
alphabet=ALPHABET,
numCharacters=8,
validationRegex=VALIDATION_REGEX
}) {
for (const promise of generatePassword(domain, userId, password, nonce, numCharacters, alphabet, validationRegex)) {
const password = await promise;
if (password) return password;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment