Skip to content

Repository files navigation

readablePassphraseJS

Javascript implementation of Murray Grant's readable passphrase generator

Readable Passphrase Generator creates random english sentences. They may be easier to remember than a long string of random letters & numbers, or 4 random words.

About the generator & Licensing

Basic usage

In a browser

Build the browser bundle (npm install && npm run build), then include it on your web page: <script type='text/javascript' src='dist/readable-passphrase.global.js'></script>

This exposes three globals: ReadablePassphrase, RPMutator, and RPSentenceTemplate (the internal-only RPWord/RPWordList*/RPRandomFactors classes are not exposed).

Then you can get a passphrase object:

varmyPhrase=newReadablePassphrase('random');// call with the name of a template, eg 'normal' or 'random'console.log(myPhrase.toString());// show the generated phrase, eg "an orchid will oversee the fig"

In Node / from npm

importReadablePassphrasefrom'readable-passphrase';// ESM// const { default: ReadablePassphrase } = require('readable-passphrase'); // CJSconstmyPhrase=newReadablePassphrase('random');console.log(myPhrase.toString());

If you just want a basic random phrase, use these templates:

  • 'randomShort' -> very short phrases, can be easily cracked in a day or so
  • 'random' -> medium strength phrase
  • 'randomLong' -> high-strength phrase
  • 'randomForever' -> very high-strength phrase

You can get a list of all available predefined templates:

vartemplateNames=ReadablePassphrase.templates();// returns an array: [ 'random', 'randomShort', ... 'normal' ]

Templates

ReadablePassphrase uses a sentence template to generate a phrase.

'normal' is the name of a predefined template that generates a basic noun, then a verb, then another noun.

A template part may come out as multiple words in the final phrase.

The phrase "an orchid will oversee the fig" breaks down as

  • noun: "an orchid"
  • verb: "will oversee"
  • noun: "the fig"

A sentence template is an array of part objects. Each object has a 'type' property.

Currently allowed types: noun, verb, conjunction, directSpeech

Noun and Verb have several modifiers to determine the final form of the word.

Noun:

  • subtype [choice: common, proper, nounFromAdjective] - form of the noun
  • article [choice: none, definite, indefinite, demonstrative, personalPronoun ]
  • adjective [boolean] - whether to include an adjective
  • preposition [boolean] - whether to include a preposition
  • number [boolean] - whether to add a number before the noun, eg "234 dogs"
  • singular [boolean] - whether the noun is singular (plural if false)

Verb:

  • subtype [choice: present, past, future, continuous, continuousPast, perfect, subjunctive ]
  • adverb [boolean] - whether to include an adverb
  • interrogative [boolean] - whether to make the whole phrase interrogative
  • intransitive [choice: noNounClause, preposition] ** both choices can be 0

Modifiers

When a modifier (called a 'factor' in the code) is a 'choice', it is specified as an object with the choices as properties, and each property has a numeric weight value. Example:

{common: 1,proper: 4,nounFromAdjective: 0}

When the engine evaluates the choice, it randomly picks one of the properties, biased toward properties with higher weights. In the example above, it would choose:

  • common: 20% [1 in 5]
  • proper: 80% [4 in 5]
  • nounFromAdjective: 0%

It is possible for all choices to be 0, in which case the choice evaluates to 'null'. Only the 'intransitive' property of verbs expects this, in other cases, if all choice weights are 0, it will cause the engine to abort with an error.

When a modifier is a boolean, it can be specified in two ways:

  1. as a 2-element array: [ trueWeight, falseWeight ], eg [ 1, 4 ] evaluates true 1 in 5 times
  2. as a boolean. true is equivalent to [ 1, 0 ] and false is equivalent to [ 0, 1 ]

Sample templates parts

// A simple transitive verb, with a slight chance of being interrogative{type: 'verb',subtype: {present: 10,past: 8,future: 8,continuous: 0,continuousPast: 0,perfect: 0,subjunctive: 0},adverb: false,interrogative: [1,8],intransitive: {noNounClause: 0,preposition: 0}}// A common, singular noun{type: 'noun',subtype: {common: 1,proper: 0,nounFromAdjective: 0},article: {none: 5,definite: 4,indefinite: 4,demonstrative: 0,personalPronoun: 2},adjective: false,preposition: false,number: false,single: true}// conjunctions and directSpeech take no other modifiers{type: 'conjunction'}{type: 'directSpeech'}

To see an existing template, execute this in the console:

console.log(RPSentenceTemplates.byName('normal'));// some other templates: strongRequired, insaneSpeech

Dynamic Loading

You can dynamically load this library instead of including it as part of the page. This will allow the page to load faster, and you can save memory by not loading libraries that may not be needed every time. Your javascript can generate a script tag with src=this library, then append it to the page.

When this library finishes loading, it will call a function named ReadablePassphrase_Callback() You can define this function to do whatever you like, such as enabling UI elements, replacing the default randomness source, or generating some initial phrases.

If this function does not exist, nothing will happen.

Randomness

All random numbers come from a function called ReadablePassphrase.randomness(). By default, this uses globalThis.crypto.getRandomValues() — a cryptographically strong source available natively in modern browsers and in Node.js 19+. It only falls back to Math.random() (with a one-time console warning) if globalThis.crypto is genuinely unavailable in your environment; Math.random() is not cryptographically secure and should not be relied on for generating passwords.

If you want to supply your own randomness source (eg. gathering entropy from mouse movements, a hardware RNG, random.org, etc.), replace ReadablePassphrase.randomness after the library has finished loading. The function should accept 1 numeric parameter and output a floating-point number between 0 and the parameter (including 0, but not including the parameter itself; eg: parameter = 5 should return values between 0 and 4.9999999).

Example:

ReadablePassphrase.randomness=function(maxValue){varrandomValues=newUint32Array(1);window.crypto.getRandomValues(randomValues);return(randomValues[0]*(maxValue||1)/0xFFFFFFFF);}

One good public javascript randomness library is the Stanford Javascript Crypto Library SJCL

Mutators

By default, phrases are all lowercase and do not contain punctuation, but might contain numbers if the template calls for them. To make the phrase more secure, it would be a good idea to add random capital letters and throw in some numbers. This will make the phrase harder to remember, but a lot more secure.

  • Randomly capitalizing one entire word makes your password about 5x harder to crack
  • A single number added to the end of a random word makes your password about 50x harder to crack

You can do this yourself after you choose your phrase; just think of a number or pick a word and make the modification when you use it. However, this module can do this task for you as well.

Pass a second parameter to ReadablePassphrase when creating the object:

varmutator={upper: {type: 'WholeWord',count: 1},numbers: {type: 'EndOfWord',count: 2}};varmutantPhrase=newReadablePassphrase('random',mutator);console.log(mutantPhrase.toString());// the seashell IS5 signalling9 a windpipe

A mutator is an object with the properties seen above. 'upper' describes how to add uppecase letters, and 'numbers' describes how and where to add numbers. 'count' is the number of modifications to make. An unlimited number of numbers can be added, but 'upper' will not add extra words if its count is higher than the number of words.

If count is 0, null or not specified, 'upper' will randomly choose the number of words to modify, and numbers will add 1-5 numbers. The 'type' determines how/where the modification will be made.

upper types:

  • StartOfWord - the first letter of the word
  • WholeWord - the entire word
  • Anywhere - one random letter in the word
  • RunOfLetters - 2 or more letters next to each other in the word
  • random - any of the above, chosen randomly
  • none - no letters will be made uppercase

numbers types: (these determine where a number will be added)

  • StartOfWord - the beginning of a word (eg 5flower)
  • EndOfWord - end of a word (flower3)
  • StartOrEndOfWord - either the start or end of the word (50/50 chance)
  • EndOfPhrase - end of the sentence (the flowers grind a cat2)
  • Anywhere - anywhere (flo2wer)
  • random - same as 'anywhere'
  • none - no numbers will be added

There are two predefined mutators:

  • 'standard' - 1 uppercase word + 2 numbers (added to the end of words)
  • 'random' - completely random mutations (phrase will be hard to remember)

You can use the predefined mutators by passing their name as a string:

varmutantPhrase=newReadablePassphrase('random','standard');console.log(mutantPhrase.toString());// the seashell IS5 signalling9 a windpipe

Entropy

For certain purposes, it is useful to know how much entropy (randomness) is in a template. Entropy is expressed as bits, where each bit represents a 50/50 chance.

Flipping a standard coin gives you 1 bit of entropy.
Choosing a random noun from a list of 3800 nouns gives you almost 12 bits of entropy (as if you had flipped a coin 12 times).

You can get an estimate of how much entropy is in any template:

varentropyOfNormal=RPSentenceTemplates.entropyOf('normal')// 27.74

If an attacker knows how you generated your password, they can guess your password in [ 2 ^ (entropy - 1) ] tries, on average. If you flip 2 coins, you have 4 possible results (heads/heads, heads/tails, tails/heads, tails/tails), but someone trying to guess your result will guess it in about 2 tries (on average). Sometimes they will guess it right away on the first try, and other times it will take all 4 tries.

The 'normal' template is short, with about 27.74 bits of entropy. This means an attacker would guess it in [ 2 ^ 26.74 ] tries = 112,083,603 . 112 million sounds like a lot, but it isn't when an attacker could reasonably be expected to guess about 10,000 combinations per second.

Each added bit doubles the possibilities, and also doubles the amount of time it would take an attacker to guess your combination.

If you use a mutator, those have entropy too. You can add the entropies together:

varentropyOfStandard=newRPMutator('standard').entropy()// 16.15

So normal template + the standard mutator together have about 43.89 bits, which takes the possible combinations from 112 million (easily crackable) to 1.6 trillion which will be a lot more annoying. Still, that's something someone could crack within your lifetime, so it's better to use stronger templates than 'normal'.

Compression

The noun and verb dictionaries are compressed to reduce the size of what ships in dist/ (this roughly halves the gzip-compressed size of the browser bundle). This is a build-time step, not something you need to think about as a consumer of the library — the wordList objects reconstruct the full dictionary from the compressed form when the library loads.

If you want to edit the dictionaries (eg. to add a verb), edit the human-readable, fully-spelled-out source in src/dictionary/source/nouns.js (singular/plural pairs) and verbs.js/intransitive-verbs.js (all 14 tenses spelled out). npm run build (or npm test) compresses these into src/dictionary/generated/ automatically before bundling; you never need to hand-compress anything.

About

Javascript implementation of Murray Grant's readable passphrase generator

Resources

Stars

3 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages