Skip to content

Instantly share code, notes, and snippets.

@kitihounel
Created July 15, 2024 20:04
Show Gist options
  • Save kitihounel/100fdfe9c845bbdb0fcb2d08c531a3e3 to your computer and use it in GitHub Desktop.
Save kitihounel/100fdfe9c845bbdb0fcb2d08c531a3e3 to your computer and use it in GitHub Desktop.
Funny little NodeJS script to generate passwords
/**
* This is a small program used to generate passwords given an alphabet and a target length.
*
* A bit of math and programming.
* We have an alphabet of size A and we want a password of length P.
* We need to generate an integer bound by (A^P) and convert it to base A using the standard base
* conversion algorithm with our alphabet symbols as the digits.
*
* (A^P) is our upper bound because (A^P) has P+1 digits in base A and we need P digits (symbols).
* For example, in base 10, 10^2 = 100 has 3 digits. In base 2, 16 = 2^4 = 10000 has 5 symbols, etc.
*
* We will use `crypto.randomBytes` to generate random bits and convert them to an integer.
* The problem we need to solve is how many bits (bytes) do we *really* need to generate ?
* The number of bits needed to store (A^P) is B = floor(P * log2(A)) + 1. So we need to generate B-1 bits.
* We will just call `crypto.randomBytes` to get ((B-1) / 8) bytes and the hard part should be done.
* But there is still a small catch. (B-1) / 8 is not always an integer, so we round to the next integer
* before calling `crypto.randomBytes`, which means that we will have more bits than we need. Our password
* will have sometimes P+1 characters. We handle this stopping the base conversion algorithm as soon as
* we reach P symbols for our password.
*/
const crypto = require('crypto');
const debug = true;
const alphabet = String.raw`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&*()_-+={[}]|:;"'<,>.?/`;
const pwLength = 18;
function computeNumberOfBytes(pwLength, alphabetSize) {
const bitCount = Math.ceil(pwLength * Math.log2(alphabetSize));
if (debug) {
const s = `${bitCount} bits required for pw of length ${pwLength} with alphabet of size ${alphabetSize}`;
console.debug(s);
}
return Math.ceil(bitCount / 8);
}
function toPassword(n, alphabet, pwLength) {
if (debug) {
console.debug('Generating password from', n);
}
const base = BigInt(alphabet.length);
const symbols = [];
while (n != 0n && symbols.length < pwLength) {
const d = Number(n % base);
symbols.push(alphabet[d]);
n /= base;
}
if (n != 0n && debug) {
console.debug('Case of remaining bits detected', n);
}
return symbols.reverse().join('');
}
const byteCount = computeNumberOfBytes(pwLength, alphabet.length)
const bytes = crypto.randomBytes(byteCount);
const s = bytes.toString('hex');
const n = BigInt(`0x${s}`);
console.log('Password:', toPassword(n, alphabet, pwLength));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment