Skip to content

integral-dw/dw-passphrase-generator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

dw: A Diceware Passphrase Generator for Emacs

https://melpa.org/packages/dw-badge.svg https://stable.melpa.org/packages/dw-badge.svg

https://imgs.xkcd.com/comics/password_strength.png

Table of Contents

About

This package provides a basic implementation of the diceware method of passphrase generation, by Arnold G. Reinhold. If you are new to diceware, please read the subsections below, or check out the link above.

A minor disclaimer: I am not an expert on encryption, cybersecurity, infosec, psychology or information theory. But I do have a mathematically solid enough background to understand explanations, remarks and arguments regarding password security fundamentals. So if anything in this README is badly explained, half-true or plain wrong, please consider opening an issue.

What does this package do?

All it really does is automate away the table lookup part of the diceware method, so you don’t have to C-s every five rolls. In other words, the core premise of this package is so simple, it could snugly fit into less than 150 LOC. Add safety checks, core facilities for translating die rolls to random integers of nigh-arbitrary range, a few public API functions and a couple of customizable bits, and you’re left with a factor of 2-4 of that.

What is diceware?

In short, diceware is a way of generating secure passphrases. While this README will briefly describe the basics, it does not try to replicate the original home page.

What is a passphrase, and why should I use it?

If a password is a sequence of random characters, a passphrase is simply a sequence of random words. In this context, a “word” can be a proper word from a real language, or a short sequence of random characters. See the above comic for a simple example of (bad) passwords versus simple passphrases. A few of the selling points of passphrases are:

  • They are easier to memorize.
  • They are easy to generate (even without this package).
  • They are prescriptive: Roll dice, go through the list, write down the word, repeat. Everything about the method is straightforward.

What is a wordlist and why do I need one?

Unlike passwords, which draw from a random pool of characters for security, diceware passphrases draw from a random pool of words. A wordlist associates five die rolls with a single word, meaning every regular wordlist is 6^5 or 7776 words long. While there are wordlists with fewer dice, I see very little point supporting them, as it just means having to roll more words for the same security. While there are fancy wordlists in PDF and other formats, dw is limited to plain text. Here is a small excerpt from one of the wordlists linked in the Installation section, as a simple example of how a supported wordlist looks like:

36166	limb
36211	limeade
36212	limelight
36213	limes
36214	limit
36215	limping
36216	limpness
36221	line
36222	lingo
36223	linguini

And so on. In other words, a wordlist is a plain text file where each line begins with a unique sequence of dice rolls, followed by whitespace, followed by the word to replace the specific result with.

What makes a passphrase secure?

As described above, a passphrase draws from a pool of random words, not random characters. A simple way of measuring security is called entropy, which is defined as the base 2 logarithm of the number of possible outcomes for a generated passphrase. So it essentially measures the expected order or magnitude of guesses an attacker would need even if they knew how the passphrase was generated. Since the number of possibilities grows multiplicatively with the number of words added, the entropy grows additively. A password generated by picking a random sequence of printable ASCII characters has log2(95) or ≈6.6 “bits” of entropy per character. Likewise, a passphrase has 5*log2(6) or ≈12.9 bits of entropy per word, meaning a six-word passphrase is about the same quality as a perfectly random string of 12 printable characters, a seven word passphrase is equivalent to 14. The trade-off that arises is between having to type “fill 33rd chi drury behind chick fade” and having to remember “9W]OZ<D`zBnDpf”.

What are the limitations of diceware?

There are two relevant factors: The first is arbitrary restrictions such as the password requiring at least one numeral, uppercase letter or special character. This problem can be easily overcome by random character insertion or adding salt (neither of which is currently implemented, but explained here).

The other problem is a limited password length. If the system you need a password for does not let you exceed a limit of 15 characters, you’re basically stuck with random character strings and ideally drawing from a large alphabet of characters.

Remark: If your passphrase approximates a known phrase, drop it, and re-roll. The chance of such a passphrase being generated is astronomically low, but every sensible attacking strategy would try common phrases.

Can’t I just guess numbers instead of rolling dice?

NO. The human perception of randomness is immensely skewed, and rather predictable. Humans are too good at recognizing patterns, so people are not going to produce the sequences “22226” and “13426” with equal probability. One somehow feels “more random” than the other, doesn’t it? The diceware homepage (linked above) also has recommendations for cases where dice are not available.

How to Use This Package

Once the package is set up (see Installation), basic usage boils down two three simple steps:
  1. Open a temporary buffer. For example, C-x b dice RET opens a buffer called dice. This way you can easily dispose of the buffer once you got your passphrase.
  2. Roll your dice, reading them in some consistent way (e.g. left to right) every time, and typing them neatly separated in groups of five. You can separate them using any character matched by dw-separator-regexp (whitespace by default). For example, if you rolled ⚄⚂⚀⚅⚅, type 53166. You will need five times as many die rolls as you want words in your passphrase (six being a decent amount for normal passphrases).
  3. Mark the region where you wrote down your sequence of rolls and enter M-x dw-passgen-region RET. You may need to choose a wordlist depending on your setup (see Customization below for how to skip this step and set up a default wordlist).

    And you’re done! Copy the passphrase into a safe location, kill the buffer, and potentially purge the passphrase from your kill ring. If it is really important, write it down and store it with other sensitive documents.

    This package has a few additional extras allowing you to script your own passphrase generator without needing to worry about implementing safety-checks, wordlist parsing and lookup, as well as a few more interactive niceties. See below for a full overview.

Interactive commands

There are three simple interactive commands at the moment.

dw-passgen-region (start end &optional choose-wordlist)

The all-in-one interactive passphrase generation command, and most likely everything you’ll ever need from this package. Just mark the region containing your written down die rolls and run the command. With a prefix argument choose-wordlist, this command prompts for a wordlist to use regardless of whether a default wordlist has been specified. For more on default wordlists, see dw-named-wordlists below.

You can also set up a salt string to append to each generated passphrase (see link).

dw-set-wordlist (&optional use-default)

Manually set a wordlist without invoking dw-passgen-region, and regardless of whether a wordlist has been set for the current buffer before. The prefix argument works largely the same as that of dw-passgen-region, but with the default reversed, since it’s more likely you want to change the wordlist regardless of defaults when you go out of your way invoking an extra command to do just that.

dw-ranstring-region

Generate a random character string from die rolls. While this could in principle be used to generate regular strong passwords, it primarily serves as a minor convenience to, for example, generate a unique salt. You can customize sets of characters from which random strings are generated via dw-random-characters, which see.

Public functions

dw uses very primitive data structures to hold wordlists and internalized passphrases. Wordlists are internalized as association lists with die rolls converted to an internal integer format. The primary retrieving functions for this association list are dw-generate-passlist and dw-generate-passphrase both of which provide additional checks for safe passphrase generation.

The internal representation of a passphrase (called a passlist) is a simple list of words the passphrase is composed of, in order.

dw-build-alist (path &optional default-dir coding noerror)

This function serves to internalize and store a wordlist from within Lisp. dw-set-wordlist is a more special case of this, being solely called for the side effect of setting up dw-current-wordlist, which interactive commands should use to store the most recently used wordlist in.

This function signals the following dw-specific error:

  • dw-bad-wordlist

dw-generate-passlist (string alist &optional noerror)

Internalize a given die string to a passlist for a given wordlist. If the resulting passphrase has an extraordinarily low character count (to the point that a program brute-forcing every character combination would outperform a wordlist-based attack), this function reports a warning.

This function signals the following dw-specific errors:

  • dw-bad-roll
    • dw-too-short-passphrase
    • dw-incomplete-roll

dw-generate-passphrase (string alist &optional separator strfun)

A thin wrapper for dw-generate-passlist, concatenating the passlist into a complete passphrase. strfun allows you to apply an arbitrary string function to each word before concatenation. This is what dw-passgen-region uses to capitalize each word in a passphrase.

This function signals the following dw-specific errors:

  • dw-bad-roll
    • dw-too-short-passphrase
    • dw-incomplete-roll

dw-required-dice (n)

The minimum number of dice necessary to decide between n possible outcomes. The function itself is trivial, and only serves as a convenience to catch input errors before passing a string to dw-generate-ranint.

dw-generate-ranint (string maxint &optional noerror)

This function supports using dice to decide between maxint possible outcomes. It takes a string of die rolls and converts it to an integer between 0 (inclusive) and maxint (exclusive).

Note: If maxint is not a number of the form 2a⋅3b, dw-generate-ranint has a finite chance of failing (different from raising an error). This is unavoidable without silently increasing the odds of some values. In such cases, the function returns nil.

This function signals the following dw-specific errors:

  • dw-bad-roll
    • dw-incomplete-int
  • dw-overflow

Error types

This package defines a couple of errors, most of which may be recovered from gracefully.

dw-bad-wordlist

The wordlist cannot be used for passphrase generation. Several things may cause this error: The wordlist being too short, too long, not a regular file, or missing a key. What data the error holds depends on what went wrong:

  • Is the wordlist too long, it will hold a list of the form (> IS-LENGTH GOAL-LENGTH), where IS-LENGTH is the actual length of the wordlist, while GOAL-LENGTH is the length the list should have. Conversely, if the wordlist is too short, it will hold a list (< IS-LENGTH GOAL-LENGTH).
  • If the wordlists is missing an entry, for example the combination “16452”, it will hold this combination as a string.
  • If the file is not a regular file (e.g. a directory) it holds the predicate file-regular-p and name of the file.

dw-bad-roll

The string of die rolls cannot be parsed for some reason. More specific errors inherit from it. If signaled on its own, the string contains an invalid character. The available data is the first invalid character, as a string.

dw-incomplete-roll

The number of dice rolled is not a multiple of five (or zero). It holds two integers for data, the number of dice found and the nearest multiple of five, rounded up. Its parent is dw-bad-roll.

dw-too-short-passphrase

The number of words rolled is low enough to pose a security threat. The minimum number of words is set by dw-minimum-word-count, which see. The error holds two integers for data, the number of words the current passphrase would have and the set minimum. Its parent is dw-bad-roll.

dw-incomplete-int

The number of dice rolled is less than the theoretical minimum to uniformly sample a given range of numbers. Like dw-incomplete-roll, this error holds two integers: the number of rolls found and the theoretical minimum required. Its parent is dw-bad-roll.

dw-overflow

dw-generate-ranint is very primitively implemented, intermediately converting the given string of die rolls into a base 6 integer. This is quite inefficient for 10+ die rolls. Even worse, there is absolutely no point in requiring two extra words worth of dice for a single extra operation. Hence, this error is raised should the number of dice for a single operation exceed 10.

Installation

Since this packages relies on external files, a minimum installation requires two (plus a third, optional) steps:

  1. Put dw.el into your load path.
  2. Put a wordlist for passphrase generation into the directory specified by dw-directory (see the section Customization for more). You can find English wordlists here and here. The former generates passphrases with long, common words while the latter favors short words and letter combinations, which may be harder to remember but quicker to type. You can find wordlists for many other languages here. See the section Wordlists for more details on what kind of wordlists dw expects.
  3. (optional) Set up dw-named-wordlists (see the section Customization below).

    If you do not wish to download extra files to use this package, you can use the “preinstalled” word list from the EFF by adding the following to your configuration:

    (with-eval-after-load 'dw
      (setq-default dw-current-wordlist dw-eff-large))
        

Customization

dw-directory

The wordlist directory. Upon load, this package automatically generates whichever directory this variable is set to, if it doesn’t exist yet. The default directory is ~/.emacs.d/diceware, or a system-specific equivalent thereof. You can either customize or set this variable manually.

Note: Setting this variable outside of custom (for example with setq) must be done before the package is loaded if you want to have the directory auto-generated. Otherwise, dw will generate the default directory instead.

dw-named-wordlists

By default, dw-passgen-region will prompt you for a wordlist file to use. However, most of the time you’ll want to use the same wordlist. For this reason, this package lets you define named wordlists to speed up the selection process.

dw-named-wordlists is an association list where each entry is of the form (NAME FILE . CODING) or simply (NAME FILE). NAME should be a symbol. FILE should be the filename of the wordlist, either relative to dw-directory or absolute. CODING should be the encoding of the file, with nil being treated as utf-8.

The symbol default is a special wordlist name: if used, this wordlist will be selected by default without prompting the user.

dw-separator-regexp

Regular expression matching a single separator character. All characters matching this regexp are ignored when reading die rolls, making them valid separators for noting down. Separators are useful for visually grouping die rolls. For example, the string

“13524 23621 63622”

is more clearly a set of 15 die rolls than “135242362163622”. Customize this value to include whichever characters you fancy. For example, use (setq dw-separator-regexp "\\(\\s-\\|[.,-]\\)") to also allow for periods, dashes and commas.

dw-passphrase-separator

By default, the words making up the passphrase generated by dw-passgen-region are separated by spaces. This is done to prevent word collisions (cases where two words concatenated yield another valid word, like “in”+”put” → “input”). You can change the separator by setting this variable, or omitting it entirely (using the empty string). Ultimately, the choice of separator makes very little difference. It is, however, best to choose a separator once and stick to it, or else it becomes additional needless information to memorize, which the diceware method tries to keep to a minimum.

dw-salt

Salt is a string of non-secret data to append to your passphrases. It serves to prevent dictionary attacks, and makes it harder for potential attackers to brute force multiple keys at once.

While it is not a good idea to use the same passphrase for everything, it is best to use the same salt or everything, as it frees precious mental real estate. You can use a phone number, a random string of characters, or anything else for this purpose, as long as it is sufficiently unique. In that sense, you can think of a salt as you adding a personal signature to your passphrase.

On a more practical note, it is also a great way to fulfill those pesky demands of some services to have a special character, a number and an uppercase character in it without adding mental overhead.

dw-use-salt

This variable serves as a flag for whether you want your salt to be automatically appended to a newly generated passphrase. Of course, if dw-salt is nil, this variable has no effect. The following symbols have special meaning (with any other value being equivalent to t):

  • t: Always add the salt, without asking.
  • prompt: Ask whether to add salt after passphrase-generation.
  • nil: Never add salt.

dw-random-characters

This variable is used by dw-ranstring-region to generate random strings from die rolls. It is an association list where each entry has the form (NAME STRING . LAX). STRING serves as a set of characters to choose from when processing die rolls. NAME is the name the string will be referred to on input. Finally, LAX is a small security flag: If nil, the string must be a power of 6 long (6, 36, …). That is because it is the only useful way to ensure that every possible sequence of die rolls maps to a result with equal probability. Otherwise, some die rolls must be discarded to avoid bias. If LAX is non-nil, some die rolls will simply be discarded, meaning it will take you (on average) a fraction of a die per rolled character more.

Remark: Keep in mind that the number of characters in string decides the number of dice you have to roll per character. 1-6 characters need one die roll, 7-36 need two, etc.

dw-capitalize-words

One of the more common restrictions put on passphrases is the requirement of at least one capital letter. However, the entropy gained from random capitalization is not worth the effort unless you are dealing with a tight character limit for your passphrase (at which point you are basically stuck using random character strings for decent security anyway). So this option simply capitalizes every word in your passphrase. As with setting a separator (see above), you should use one way of capitalization and stick to it. It makes no sense to change this variable often, as this turns a convenience into a burden to remember.

Remark: The original FAQ recommends randomly capitalizing one word in the passphrase to fulfill this condition. Since there is hardly a way to implement this in a way taking fewer key strokes than it would take to do it manually, this option is not implemented.

dw-minimum-word-count

As technology marches on, passphrases need to become harder to guess to prove effective. This variable ensures that you don’t create an insufficiently long passphrase by accident. The current value is 5, corresponding to the number of words of a sub-par passphrase. You may want to set this value to 6 to be on the safe side.

Note: There is no real point in setting this variable any lower (or higher) than 5-7. You would either render the passphrase insecure by admitting smaller passphrases (useless), or create such ridiculously high-security passphrases that the weakest link in your security shifted elsewhere: A fifth bolt on your front door won’t do you any good if you keep your ground floor windows open.

NEWS

2021-04-01

Version 1.1.0 has been released! The new version added a “preinstalled” internalized diceware wordlist dw-eff-large, to allow the package to be used in a more self-contained way.

Archive

2020-10-06

Version 1.0.0 is finally out! The new version fixes a few glaring errors in random integer generation (passphrases remain unaffected, no worries), adds salt as an option upon passphrase generation and allows you to generate random character sequences with die rolls.

2020-09-21

The package is now available on MELPA! A few minor additions will be made soon, at which point this package gets its official 1.0.0 upgrade!

2020-09-15

I have finally decided to make this package public. This is probably my oldest project, and has seen three complete rewrites by now. Now that I am somewhat more experienced with writing packages, I decided to refurbish the old thing once more, and potentially getting it on MELPA. For now, I will focus on finishing up minor features and adding a bit of polish.