Skip to content

Commit 8b2d410

Browse files
authored
Merge pull request #2 from benjreinhart/passphrases
Add support for memorable passphrases
2 parents f7db889 + 7581e4b commit 8b2d410

File tree

7 files changed

+8174
-81
lines changed

7 files changed

+8174
-81
lines changed

README.md

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# secure-password-utilities ![Github CI](https://github.com/benjreinhart/secure-password-utilities/workflows/Github%20CI/badge.svg)
22

3-
Secure, zero-dependency utilities for generating passwords, pins, and more.
3+
Secure, zero-dependency utilities for generating passwords, passphrases, pins, and more.
44

55
* 0️⃣ Zero dependencies
66
* 💯 Works in browsers (using _webcrypto_) and node 12.x+ (using _node:crypto_)
@@ -30,6 +30,7 @@ console.log(pin); // 036919
3030

3131
- [secure-password-utilities](#secure-password-utilities)
3232
- [generatePassword](#generatepassword)
33+
- [generatePassphrase](#generatepassphrase)
3334
- [generatePin](#generatepin)
3435
- [generateCharacters](#generatecharacters)
3536
- [secure-password-utilities/constants](#secure-password-utilitiesconstants)
@@ -40,13 +41,17 @@ console.log(pin); // 036919
4041
- [secure-password-utilities/csprng](#secure-password-utilitiescsprng)
4142
- [getRandomBytes](#getrandombytes)
4243
- [secure-password-utilities/random](#secure-password-utilitiesrandom)
44+
- [getRandomNumbersInRange](#getrandomnumbersinrange)
4345
- [getRandomValues](#getrandomvalues)
4446
- [randomizeCharacters](#randomizecharacters)
47+
- [secure-password-utilities/wordlists](#secure-password-utilitieswordlists)
48+
- [DEFAULT_WORDLIST](#default_wordlist)
49+
- [EFF_LONG_WORDLIST](#eff_long_wordlist)
4550

4651
### secure-password-utilities
4752

4853
```ts
49-
import {generatePassword, generatePin, generateCharacters} from 'secure-password-utilities'
54+
import {generatePassword, generatePassphrase, generatePin, generateCharacters} from 'secure-password-utilities'
5055
```
5156

5257
#### generatePassword
@@ -57,8 +62,6 @@ function generatePassword(length: number, options?: PasswordOptionsType): string
5762

5863
Generates a random password.
5964

60-
Uses a CSPRNG for randomness.
61-
6265
`PasswordOptionsType` is defined as:
6366

6467
```ts
@@ -129,6 +132,29 @@ const evenDigitPassword = generatePassword(12, {
129132
console.log(evenDigitPassword); // e6V8zy0kfTAN
130133
```
131134

135+
#### generatePassphrase
136+
137+
```ts
138+
function generatePassphrase(length: number, wordlist: readonly string[], sep?: string): string
139+
```
140+
141+
Generate a memorable passphrase comprised of words chosen randomly from the given wordlist.
142+
143+
There are wordlists available in the [wordlist module](#secure-password-utilitieswordlists), or you can provide your own.
144+
145+
```ts
146+
import {DEFAULT_WORDLIST} from 'secure-password-utilities/wordlists';
147+
148+
generatePassphrase(6, DEFAULT_WORDLIST); // canopener-uncanny-hatchet-murky-agony-traitor
149+
generatePassphrase(6, DEFAULT_WORDLIST); // backpack-craftwork-sweat-postcard-imaging-litter
150+
```
151+
152+
The word separator defaults to a dash (`-`), but you can customize this behavior using the third argument.
153+
154+
```ts
155+
generatePassphrase(6, DEFAULT_WORDLIST, '_'); // goldfish_scorpion_antiviral_pursuit_demanding_motto
156+
```
157+
132158
#### generatePin
133159

134160
```ts
@@ -137,8 +163,6 @@ function generatePin(length: number): string
137163

138164
Generate a random digit pin.
139165

140-
Uses a CSPRNG for randomness.
141-
142166
```ts
143167
generatePin(6); // 036919
144168
generatePin(8); // 45958396
@@ -152,8 +176,6 @@ function generateCharacters(length: number, charset: string): string
152176

153177
Generate a string of `length` characters chosen randomly from the given `charset`.
154178

155-
Uses a CSPRNG for randomness.
156-
157179
```ts
158180
generateCharacters(4, '$%^&'); // &$&^
159181
generateCharacters(6, '0123456789'); // 947682
@@ -211,11 +233,29 @@ Generates random bytes. This is a wrapper around the platform's native CSPRNG. I
211233
### secure-password-utilities/random
212234

213235
```ts
214-
import {getRandomValues, randomizeCharacters} from 'secure-password-utilities/random'
236+
import {getRandomNumbersInRange, getRandomValues, randomizeCharacters} from 'secure-password-utilities/random'
237+
```
238+
239+
#### getRandomNumbersInRange
240+
241+
```ts
242+
function getRandomNumbersInRange(length: number, start: number, end: number): number[]
243+
```
244+
245+
Get a list of random numbers where each number is greater than or equal to `start` and less than `end`.
246+
247+
The `end` of the range must be less than or equal to 2^16.
248+
249+
```ts
250+
getRandomNumbersInRange(6, 0, 10) // [8, 2, 1, 3, 5, 0]
251+
getRandomNumbersInRange(6, 10, 20); // [ 18, 10, 13, 12, 12, 19 ]
252+
getRandomNumbersInRange(6, 0, 1000); // [111, 752, 41, 420, 360, 630]
215253
```
216254

217255
#### getRandomValues
218256

257+
*Note: This is deprecated, use `getRandomNumbersInRange` instead.*
258+
219259
```ts
220260
function getRandomValues(numValues: number, rangeMax?: number): Uint8Array
221261
```
@@ -232,14 +272,41 @@ function randomizeCharacters(characters: string): string
232272

233273
Randomize the ordering of the characters in the given string.
234274

235-
Uses a CSPRNG for randomness.
236-
237275
```ts
238276
randomizeCharacters('randomize me'); // e znmaedimro
239277
randomizeCharacters('randomize me'); // arndimz moee
240278
randomizeCharacters('randomize me'); // ai emdonmrze
241279
```
242280

281+
### secure-password-utilities/wordlists
282+
283+
```ts
284+
import {DEFAULT_WORDLIST, EFF_LONG_WORDLIST} from 'secure-password-utilities/wordlists'
285+
```
286+
287+
#### DEFAULT_WORDLIST
288+
289+
```ts
290+
const DEFAULT_WORDLIST = Object.freeze([/* EFF long wordlist minus a few entries (see below) */]);
291+
```
292+
293+
This is the "default" wordlist for use with this library. It is the same as the EFF long wordlist but with the following entries removed:
294+
295+
* drop-down
296+
* flet-tip
297+
* t-shirt
298+
* yo-yo
299+
300+
The reason for this is that a frequent passphrase separator is the "-" which can then result in ambiguous word separations. This keeps the resulting passphrase prettier (in the case where it's joined by dashes) with an unambiguous and deterministic number of dashes.
301+
302+
#### EFF_LONG_WORDLIST
303+
304+
```ts
305+
const EFF_LONG_WORDLIST = Object.freeze([/* EFF long wordlist, see https://www.eff.org/files/2016/07/18/eff_large_wordlist.txt */]);
306+
```
307+
308+
The [EFF recommended wordlist](https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases) for passphrases.
309+
243310
## License
244311

245312
The MIT License (MIT). See [LICENSE file](LICENSE).

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "secure-password-utilities",
33
"version": "0.1.0",
4-
"description": "Secure, zero-dependency utilities for generating passwords, pins, and more",
4+
"description": "Secure, zero-dependency utilities for generating passwords, passphrases, pins, and more",
55
"keywords": [
66
"password",
77
"pin",

src/index.ts

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { getRandomValues, randomizeCharacters } from 'secure-password-utilities/random';
1+
import {
2+
getRandomValues,
3+
getRandomNumbersInRange,
4+
randomizeCharacters,
5+
} from 'secure-password-utilities/random';
26
import {
37
DIGIT_CHARSET,
48
LOWERCASE_CHARSET,
@@ -30,7 +34,7 @@ export type PasswordOptionsType = {
3034
/**
3135
* Generate a random password.
3236
*
33-
* Uses a CSPRNG for randomness.
37+
* Examples:
3438
*
3539
* generatePassword(12); // l[Nz8UfU.o4g
3640
* generatePassword(8, { symbols: false, digits: 2 }); // k9WTkaP6
@@ -212,7 +216,7 @@ function validateCharsetOption(name: string, charset: string) {
212216
/**
213217
* Generate a random digit pin.
214218
*
215-
* Uses a CSPRNG for randomness.
219+
* Examples:
216220
*
217221
* generatePin(6); // 036919
218222
* generatePin(8); // 45958396
@@ -233,7 +237,7 @@ export function generatePin(length: number) {
233237
/**
234238
* Generate a string of `length` characters chosen randomly from the given `charset`.
235239
*
236-
* Uses a CSPRNG for randomness.
240+
* Examples:
237241
*
238242
* generateCharacters(4, '$%^&'); // &$&^
239243
* generateCharacters(6, '0123456789'); // 947682
@@ -260,3 +264,44 @@ export function generateCharacters(length: number, charset: string) {
260264
return characters + charset[i];
261265
}, '');
262266
}
267+
268+
/**
269+
* Generate a memorable passphrase comprised of words chosen randomly from the given `wordlist`.
270+
*
271+
* There are wordlists available in the wordlists module, or you can provide your own.
272+
*
273+
* The word separator defaults to a dash (`-`), but you can customize this behavior using the third argument. "-"
274+
*
275+
* Examples:
276+
*
277+
* generatePassphrase(6, DEFAULT_WORDLIST); // canopener-uncanny-hatchet-murky-agony-traitor
278+
* generatePassphrase(6, DEFAULT_WORDLIST); // backpack-craftwork-sweat-postcard-imaging-litter
279+
* generatePassphrase(6, DEFAULT_WORDLIST, '_'); // goldfish_scorpion_antiviral_pursuit_demanding_motto
280+
*
281+
* @param length The number of words selected at random.
282+
* @param wordlist The list of words to sample from.
283+
* @param sep The separator to use when joining the words in the passphrase. Defaults to '-'.
284+
* @returns A memorable passphrase.
285+
*/
286+
export function generatePassphrase(length: number, wordlist: readonly string[], sep = '-') {
287+
if (typeof length !== 'number' || length < 1) {
288+
throw new Error(
289+
'Invalid argument: length argument must be a number greater than or equal to 1'
290+
);
291+
}
292+
293+
if (!Array.isArray(wordlist) || wordlist.length < 2) {
294+
throw new Error(
295+
'Invalid argument: wordlist argument must be an array with length greater than or equal to 2'
296+
);
297+
}
298+
299+
if (typeof sep !== 'string') {
300+
throw new Error('Invalid argument: sep argument must be a string');
301+
}
302+
303+
return getRandomNumbersInRange(length, 0, wordlist.length).reduce((passphrase, value, i) => {
304+
const word = wordlist[value];
305+
return passphrase + (i === 0 ? word : sep + word);
306+
}, '');
307+
}

0 commit comments

Comments
 (0)