Created
July 15, 2024 20:04
-
-
Save kitihounel/100fdfe9c845bbdb0fcb2d08c531a3e3 to your computer and use it in GitHub Desktop.
Funny little NodeJS script to generate passwords
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
/** | |
* 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