Created
March 15, 2020 13:57
-
-
Save Ravenstine/8053651dd2aeacee9973b9f93a892311 to your computer and use it in GitHub Desktop.
Master Key - A stateless password manager
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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