Password Generator
A Typescript library for generating passwords using configurable alphabets and a configurable distribution of said alphabets.
Installation
npm install @llftr/password-generator
OR
yarn add @llftr/password-generator
Usage
For basic usage it's pretty straight forward. Just import the main class and use it to generate passwords. Here's the TLDR version:
import { PasswordGenerator } from '@llftr/password-generator';
const generator = new PasswordGenerator();
/*
* Generate a 16 character password that contains any combination of the following:
* - lowercase alpha characters
* - uppercase alpha characters
* - digits
* - symbols
*/
const password = generator.generate();
/*
* Same as above, 16 characters, but guaranteed to contain at least one of each.
*/
const secondPassword = generator.generateWithAllAlphabets();
The whole generation process is configurable though. For more advanced use cases, read on.
API
Beside the main PasswordGenerator
class, there is also the RNG
class. We'll discuss the latter first, since the
whole generation process hinges on it.
RNG class
import type { IntegerGenerator } from '@llftr/password-generator';
import { RNG } from '@llftr/password-generator';
const myGenerator: IntegerGenerator = (min, max) => {
// Your code here
};
function alsoValid(min: number, max: number): number {
// Your code here
}
const defaultRng = new RNG();
const myCustomRng = new RNG(myGenerator);
const myOtherCustomRng = new RNG(alsoValid);
The RNG class constructor takes an optional IntegerGenerator
as a parameter. This is a type alias which is defined as
such:
type IntegerGenerator = (minInclusive: number, maxExclusive: number) => number;
This matches the signature of crypto.randomInt()
, which is what you should be using, if you can. Wanting this package
to also be usable on the web without having to resort to polyfills and such, the default RNG
uses an existing
convenience function that relies on the Math.random()
function.
It's more than sufficient for most cases.
As seen above, you can supply your own function to it, or if you want, you could just import RNGInterface
and just
create your own custom implementation and pass that to PasswordManager
.
import type { RNGInterface } from '@llftr/password-generator';
class MyCustomRNG implements RNGInterface {
public generateInteger(minInclusive: number, maxExclusive: number): number {
// Your code here
}
generateDistribution(totalLength: number, elementCount: number, atLeastOneOfEach: boolean): number[] {
// Your code here
}
}
generateInteger(minInclusive: number, maxExclusive: number): number
This method returns a random integer in the range [minInclusive, maxExclusive). Used internally by PasswordManager
to pick randomly when generating the password.
generateDistribution(totalLength: number, elementCount: number, atLeastOneOfEach: boolean): number[]
This method creates a random distribution for use in the password generation process. It basically determines how many characters of each alphabet are used in the generation process.
const rng = new RNG();
/*
* Create a distribution for password 16 characters long and using 4 alphabets (the default parameters).
* Will return something like [2, 5, 7, 2], but also possibly [0, 3, 7, 6] or even (HIGHLY unlikely) [0, 0, 0, 16].
*/
const randomDistribution = rng.generateDistribution(16, 4);
/*
* Same as above, but guarantees no count will be less than 1.
*/
const oneOfEachDistribution = rng.generateDistribution(16, 4, true);
PasswordGenerator class
The main class of this package. It's constructor takes 2 parameters, both optional:
-
alphabets: string[]
- An array of strings from which the passwords will be generated. Defaults toPasswordGenerator.DEFAULT_ALPHABETS
. -
rng: RNG
- AnRNG
class instance. Defaults to an instance using the built-inMath.random()
method of generating integers.
DEFAULT_ALPHABETS constant
The default alphabets used are lowercase alpha, uppercase alpha, digits and symbols. You can pass in an array that adds to this one, or a different one entirely. Whatever you want, so long as it's an array of strings.
getAlphabetCount(): number
This method return the number of alphabets on this particular instance. Useful for generating custom distributions.
public generate(length: number, distribution: number[]): string
The main method of this class. Takes a length and a distribution of alphabets as parameters. Defaults to 16 characters,
and a distribution created using the supplied RNG
class, without passing in atLeastOneOfEach
.
import { RNG, PasswordGenerator } from '@llftr/password-generator';
const integerGenerator = (min: number, max: number): number => {
// Custom implementation
}
const rng = new RNG(integerGenerator);
const generator = new PasswordGenerator();
// Basic usage
const password = generator.generate();
// Supply a custom distribution. Will still use it's constructor supplied RNG for picking characters.
const customDistribution = rng.generateDistribution(20, generator.getAlphabetCount());
const otherPassword = generator.generate(20, customDistribution);
// You can even just pass a basic array of numbers. Be careful with this approach, though.
const dangerous = generator.generate(20, [5, 4, 5, 5]);
In that last example, there's actually a mistake: the element counts don't add up to the length. You should be careful when supplying custom distributions. Here be dragons.
generateWithAllAlphabets(length: number): string
Convenience method which takes only the length (default still 16) and generates a password that is guaranteed to contain
at least one character of each alphabet. It calls the supplied RNG
class' generateDistribution()
method with
atLeastOneOfEach
as true.
Are the passwords actually that random, though?
Yes. Well, good enough. The tests I created include a uniqueness test that does 10000 rounds of generation and checks for duplicates. In all the times I've run it, no duplicates have been found. As a goof, I decided to do a 500000 (half a million) rounds run. It took close to 10 minutes to do all the tests, but no duplicates were found.
Keep in mind this was done using the with default Math.random()
implementation, so even in its plum stock
configuration, the library generates sufficiently random passwords. If you want super-duper randomness, just pass
crypto.randomInt()
as the generator function to the RNG
class.
import { randomInt } from 'crypto';
import { RNG, PasswordGenerator } from '@llftr/password-generator';
// Now you have ALL the randomness.
// Well, cryptographically-secure instead of pseudo-random, so yeah, better.
const generator = new PasswordGenerator(PasswordGenerator.DEFAULT_ALPHABETS, new RNG(randomInt));
Known limitations
Yes, there are some. Namely, I was too lazy to implement error handling for every case (as of now, anyway), which means if you go too crazy with configurability, but don't take care that everything is correct, there will be errors.
One thing that springs to mind in particular is if you call PasswordGenerator.generate()
with a one length and a
distribution that whose sum is less than that length (8 length and [2, 3, 2]), the process will hang.
In it's completely stock configuration everything is perfectly fine, as the tests indicate.