45

What's a good one-liner to generate an easily memorable password, like xkcd's correct horse battery staple or a Bitcoin seed?

EDIT 1:

This is not the same as generating a random string since random strings are not at all memorable. Compare to the obligatory xkcd...

moblie
  • 569

10 Answers10

45

First of all, install a dictionary of a language you're familiar with, using:

sudo apt-get install <language-package>

To see all available packages:

apt-cache search wordlist | grep ^w

Note: all installation instructions assume you're on a debian-based OS.

After you've installed dictionary run:

WORDS=5; LC_ALL=C grep -x '[a-z]*' /usr/share/dict/words | shuf --random-source=/dev/urandom -n ${WORDS} | paste -sd "-"

Which will output ex:

blasphemous-commandos-vasts-suitability-arbor

To break it down:

  • WORDS=5; — choose how many words you want in your password.
  • LC_ALL=C grep -x '[a-z]*' /usr/share/dict/words — choose only words containing lowercase alphabet characters (it excludes words with ' in them or funky characters like in éclair). LC_ALL=C ensures that [a-z] in the regex won't match letter-like symbols other than lowercase letters without diacritics.
  • shuf --random-source=/dev/urandom -n ${WORDS} — chose as many WORDS as you've requested. --random-source=/dev/urandom ensures that shuf seeds its random generator securely; without it, shuf defaults to a secure seed, but may fall back to a non-secure seed on some systems such as some Unix emulation layers on Windows.
  • paste -sd "-" — join all words using - (feel free to change the symbol to something else).

Alternatively you can wrap it in a function:

#!/bin/bash

function memorable_password() {
  words="${1:-5}"
  sep="${2:--}"

  LC_ALL=C grep -x '[a-z]*' /usr/share/dict/words | shuf --random-source=/dev/urandom -n ${words} | paste -sd "$sep"
}

or

#!/bin/sh

memorable_password() {
  words="$1"
  if [ -z "${words}" ]; then
    words=5
  fi

  sep="$2"
  if [ -z "${sep}" ]; then
    sep="-"
  fi

  LC_ALL=C grep -x '[a-z]*' /usr/share/dict/words | shuf --random-source=/dev/urandom -n ${words} | paste -sd "$sep"
}

Both of which can be called as such:

memorable_password 7 _
memorable_password 4
memorable_password

Returning:

skipped_cavity_entertainments_gangway_seaports_spread_communique
evaporated-clashes-bold-presuming
excelling-thoughtless-pardonable-promulgated-forbearing

Bonus

For a nerdy and fun, but not very secure password, that doesn't require dictionary installation, you can use (courtesy of @jpa):

WORDS=5; man git | \
  tr ' ' '\n' | \
  egrep '^[a-z]{4,}$' | \
  sort | uniq | \
  shuf --random-source=/dev/urandom -n ${WORDS} | \
  paste -sd "-"
meeDamian
  • 582
  • 6
    There is little sense on installing a word list in Cyrillic (for example) if all Cyrillic characters are going to be rejected by the [a-z] range (in the C locale). –  Sep 21 '18 at 19:03
  • 4
    If wordlist is not available, you can use man git | tr ' ' '\n' | egrep '^[a-z.]+$' for suitably nerdy passwords. Such as "help path objects control another" or "index option foo.bar of force". – jpa Sep 23 '18 at 19:05
  • Hahaha! That's brilliant! Although you might want to remove words shorter than 3 letters with man git | tr ' ' '\n' | egrep '^[a-z.]{4,}$'. And then add the usual suffix giving you a complete: WORDS=5; man git | tr ' ' '\n' | egrep '^[a-z.]{4,}$' | shuf --random-source=/dev/urandom -n ${WORDS} | paste -sd "-". Note: It's not very secure, but it's such a fun way of achieving it! (adding it to the answer) – meeDamian Sep 24 '18 at 02:32
  • This is a terrible advice. While the password can be 100+ characters long, if they are in a dictionary, they are also in the rainbows tables. correcthorsebatterystaple might have 44 bits of entropy, but only do have it if the string generator tries to bruteforce it. If you feed it a dictionary and just compile combinations, the password is no different than a 4 character password. – Braiam Sep 24 '18 at 06:04
  • 7
    @Braiam A 4 character password, except each there could be 100000 different values for the character. log2(100000^4) is over 60 bits. – Hugoagogo Sep 24 '18 at 07:42
  • @meeDamian Why is there no need for a semicolon after LC_ALL declaration but one after WORD declaration? – Björn Sep 24 '18 at 10:00
  • 1
    @Björn Basically WORDS get scoped to the whole line, while LC_ALL only to the initial grep. See this as an example: A=dog; B=cat echo "cat\ndog" | grep "^\($A\|$B\)$" – meeDamian Sep 24 '18 at 10:45
37

I don't do this with standard utilities that are not designed with cryptographic use in mind. There is no reason to believe they're using a csPRNG or that they're seeding it properly, and someone who knows your method will be able to reproduce your passphrases. Likewise, behave of multi-purpose utilities if you aren't sure how to use them properly.

pwqgen from passwdqc.

13

You need a wordlist dictionary, since you mention bitcoin, most likely you want this one:

https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt

If your native language is not English, there are wordlists for other languages available in the same repository.

Given this english.txt file, you can do a random selection with shuf:

$ shuf -n 4 english.txt 
anchor
neck
trumpet
response

Note you'll need way more than just 4 words for a truly secure passphrase. 4 words is for an online service where the number of attempts an attacker might do is very limited. I think the bitcoin recommendation is 16 words, not sure.

Also in this example, each word may only appear once. If you wish to allow repeated words, add the --repeat option:

$ shuf -n 4 -r english.txt

That would allow each word to appear more than once.

If you want the output in one line, you can just add xargs echo:

$ shuf -n 4 english.txt | xargs echo
math easily cube daughter

Or if you prefer command substitution:

$ echo $(shuf -n 4 -r english.txt)
photo milk roast ozone

On a sidenote, I don't find this style of password to be easily memorable.

Well, actually I got very lucky with math easily cube daughter since that just happens to make it easy to think of a story where your daughter can easily do math with cubes or whatever. It's something humans can relate to, as is the horse in XKCD's example.

But what the heck is a anchor neck trumpet response? I'm not a comic book author with creativity to spare to come up with a mnemonic for that. So it will be forgotten in no time.

Even if you can remember the words, it's hard to remember their correct order. Was it math easily cube daughter or daugher easily math cube or something else?

And the password is supposed to be random, you're not allowed to pick and modify it.


As for bitcoin seeds, you're not really supposed to remember them. This is just a way to be able to write it down easily. Anyone can write down 16 words on a piece of paper and read them back correctly; with random letters it's much more likely to make mistakes.


If you have concerns about the randomness of shuf, add the --random-source=/dev/urandom parameter to all shuf commands.

See also https://www.gnu.org/software/coreutils/manual/html_node/Random-sources.html#Random-sources

frostschutz
  • 48,978
  • To allow repeated words, an alternative would be repeated invocation of shuf -n 1. That being said it might not be the worst idea to avoid suggesting a method that would allow a passphrase with only one distinct word. – David Z Sep 20 '18 at 20:00
  • @DavidZ you're right, of course. But then you have a loop and while you can write them in one line, I really prefer not to. But I just found out that shuf also has a --repeat option. Solves the issue nicely, as it allows words to be repeated any number of times. – frostschutz Sep 20 '18 at 20:28
  • Ah, of course, that is better. – David Z Sep 20 '18 at 20:32
  • The word list you link to only has ~8917 words, compared to ~63000 in (debian) /usr/share/dict/words. Maybe get a list from Wodnet (A 16Mbyte file that expands to 185747 words (after processing)). More words in the list is not really useful, each time the list doubles it only add 1 (yes one) bit of entropy per word used. Better to use more words. –  Sep 21 '18 at 00:25
  • 3
    Is the shuf command using a cryptographic random number source? Otherwise this is highly flawed. – R.. GitHub STOP HELPING ICE Sep 21 '18 at 02:38
  • Pretty much this. A user just can put words together themselves. A single shuf command is enough. No need for extra commands - less piping, less resources wasted. – Sergiy Kolodyazhnyy Sep 21 '18 at 06:22
  • 1
    @R.. shuf uses /dev/urandom or anything you like with --random-source. Can be useful if you need to shuffle related files the same way: https://unix.stackexchange.com/a/220394/30851 – frostschutz Sep 21 '18 at 08:18
  • @R.. oh, strace shows I was wrong. without --random-source, shuf "only" reads 2048 bytes from /dev/urandom, with --random-source=/dev/urandom it's considerably more (provided sufficient input), so probably some urandom-initialized prng. Haven't looked at the source. – frostschutz Sep 21 '18 at 08:29
  • @Gilles: shuf reads a nonce from /dev/urandom even if you don't pass the --random-source, so it's not THAT bad... it falls back to time, pid, ppid, uid, gid only if the /dev/urandom read fails. Or at least that's how I read coreutils lib/randread.c /* If there's no nonce device, use a poor approximation by getting the time of day, etc */ – frostschutz Sep 21 '18 at 11:11
  • @frostschutz Oh, I see what I did wrong. I read the source code but not in detail, and I tested with strace and saw that it didn't read from /dev/*random. It turns out that in my test, I was only passing a single line of input, and it seems that the RNG is initialized lazily and is never used with a single line of input. With two input lines, shuf does read from /dev/urandom. Apologies for the scare. shuf is secure to generate passwords on a Unix system with /dev/urandom. – Gilles 'SO- stop being evil' Sep 21 '18 at 11:36
  • @ImHere I use a standard Debian, without installing any special word lists, wodnet or whatever, i have 356k words in /usr/share/dict/words, not sure why you only count 63k words. – 12431234123412341234123 Jul 22 '21 at 21:21
  • @12431234123412341234123 I don't remember which version of debian was that, but what I can say now is that debian 10 (buster = stable) has a words file of 102401 lines (or words, which are equal in this case). Much less than what you report. In any case, either count supports the idea of using the OS list of words (words) instead of the github list given in this answer. Maybe you should take a look at the 16806 words from Diceware. Which is an useful list without complex (less memorable) words. –  Jul 23 '21 at 03:49
9

I have this:

xkcd_password () { 
    command grep "^[[:alpha:]]\{5,8\}$" "${1-/usr/share/dict/words}" | sort --random-source=/dev/urandom -R | head -4
}
glenn jackman
  • 85,964
  • Why {5,8}? It cuts down the number of usable words which lowers entropy. Word length isn't really important, only the number of words. – John Kugelman Sep 20 '18 at 20:50
  • 3
    I know, but shorter words are easier (for me) to remember. The EFF link I posted above recommends more shorter words as an alternative technique – glenn jackman Sep 20 '18 at 20:52
  • Maybe it could be useful to add: If the grep match grep "^[[:alpha:]]\{5,8\}$" "/usr/share/dict/words" | wc -l match 38761 words, a four words password will have bc -l <<<"4*l(38761)" bits of entropy, or ~42 bits. –  Sep 20 '18 at 22:32
  • Maybe shorter: grep "^[[:alpha:]]\{5,8\}$" file | shuf -n 4. –  Sep 20 '18 at 22:37
  • @JohnKugelman The whole word list has 62915 words with more than 3 letters (it seems unreasonable to me to use a "word" like a) and the list of 5 to 8 letters has 32057 words. It seems like a lot, but it isn't. Calculating the entropy each will have with 4 words is bc -l <<<"a=l(62915);b=l(32057);4*a/l(2);4*b/l(2)" prints 63.76 and 59.87 (rounded). That's only four bits. Not that much. –  Sep 20 '18 at 22:46
  • 2
    What sort of seed and prng does sort -R use? Probably not something suitable for this use. – R.. GitHub STOP HELPING ICE Sep 21 '18 at 02:38
  • 2
    @R.. sort -R, like shuf, uses ISAAC, which is generally accepted as a good CSPRNG even if it isn't very popular. Since coreutils 8.6, which was released in October 2010, the CSPRNG is seeded from /dev/urandom if available. – Gilles 'SO- stop being evil' Sep 21 '18 at 11:46
  • 1
    @Gilles: Is there only one implementationf of sort providing -R? Otherwise there's a risk of inadvertently using one where it's not a csprng. – R.. GitHub STOP HELPING ICE Sep 21 '18 at 12:47
  • @R.. FreeBSD also has it, but it's explicitly documented as something that should be secure if implemented properly. – Gilles 'SO- stop being evil' Sep 21 '18 at 13:05
8

Try this,

shuf --random-source=/dev/urandom -n5 /usr/share/dict/words | sed 's/[^[:alnum:]]//g' | paste -sd_

Output:

Quayles_knockwursts_scrotums_Barrie_hauler

Explanation:

  • shuf --random-source=/dev/urandom -n5 /usr/share/dict/words Get 5 random words from dict file
  • sed 's/[^[:alnum:]]//g' Remove non-alphanumeric characters
  • paste -sd_, join the words with underscore (_)

You can use any wordlist file from the Internet instead of /usr/share/dict/words.

Install the wordlist if you don't have it. It might be be in your distros repository, e.g. https://packages.debian.org/sid/wordlist:

sudo apt install wamerican
pLumo
  • 22,565
4

As suggested by @R.. but for RHEL/CentOS machines (available in the EPEL repo):

pwgen

Note this excerpt from pwgen's manpage (emphasis mine):

The pwgen program generates passwords which are designed to be easily memorized by humans, while being as secure as possible. Human-memorable passwords are never going to be as secure as completely completely random passwords.

dr_
  • 29,602
  • 1
    Sure, you can cram more bits of randomness into a password by using all printable characters. But importantly passwords don't need to be as complex as you can possibly make them - they need to be complex enough that they are not the weakest link in the security scheme. And 44 bits is not bad. If you want more, words from a bigger dictionary (>~2,000 words) should be memorable for native speakers. – l0b0 Sep 22 '18 at 08:08
  • 1
    This is incorrect: Human-memorable passwords are never going to be as secure as completely completely random passwords. Increasing the length will match any finite random requirement. –  Sep 23 '18 at 04:36
  • I'm assuming the manpage refers about passwords of equal length. – dr_ Sep 24 '18 at 06:11
  • @Isaac that's only true with very long passphrases, which given that they need to be random, would be difficult to memorize. For the adversary, splicing strings is cheap, doesn't matter whenever is the alphabet or wikipedia data dump. – Braiam Sep 24 '18 at 06:28
1

If you want it to be even easier to memorize, you can also use the password hashing approach (remember a single passphrase to generate many other passphrases from). Instead of memorizing hundreds of passphrases, you just memorize the method to generate them.

Using shuf with openssl to provide a seeded random source, you can re-generate passphrases as needed. Please note that the security of this approach depends on the strength of your passphrase alone, so make it count.

In the following example, the random seed depends on

  • your passphrase (obviously)
  • a designated purpose (user@site, wallet#number, whatever)
  • the wordlist used (hash thereof)
  • number of words requested

Change any of these and you get a different result.

get_seeded_random() {
    seed=$(printf "%s:" "$@")
    openssl enc -aes-256-ctr -pass pass:"$seed" -nosalt \
    < /dev/zero 2>/dev/null
}

get_random_words() {
    dictionary=$1
    number=$2
    passphrase=$3
    purpose=$4

    dictionary_hash=$(sha1sum < "$dictionary")

    shuf -n "$number" \
         --random-source=<(get_seeded_random
            "$passphrase" "$purpose" "$dictionary_hash" "$number") \
         "$dictionary" \
    | xargs echo
}

So, if my passphrase was WienerSchnitzel (not a good choice for obvious reasons), and if I used XKCD style passwords everywhere:

$ get_random_words english.txt 4 WienerSchnitzel wallet:1
robust lottery ugly stone
$ get_random_words english.txt 4 WienerSchnitzel wallet:2
vapor comfort various bitter
$ get_random_words english.txt 4 WienerSchnitzel frostschutz@unix.stackexchange.com
any actor tobacco tattoo

And you can execute it multiple times, always gives the same result:

$ get_random_words english.txt 4 WienerSchnitzel wallet:1
robust lottery ugly stone
$ get_random_words english.txt 4 WienerSchnitzel wallet:1
robust lottery ugly stone

But requesting one more word is a completely different result (as would be using a different dictionary, etc.):

$ get_random_words english.txt 5 WienerSchnitzel wallet:1
crash category extra hollow cloud

The downside of this approach is that, if anyone knows you're using this method, they can try to brute-force your secret passphrase and then proceed to generate all your other passphrases.

In this example, it doesn't take long to guess WienerSchnitzel. To improve this, you'd need to apply an expensive (but repeatable) hash to the passphrase itself.

# poor man's expensive hash replacement
seed=$( (echo "$seed" ; head -c 1G /dev/zero) | sha1sum)

And just use a much, much better passphrase in the first place.

You could also hard-code a truly random high-entropy password, but that completely defeats the "easily memorable" aspect of things.

Also, this implementation depends on shuf always selecting words the same way, which may not be the case for future versions in the long term.

frostschutz
  • 48,978
0

For completeness:

git clone git@gitlab.com:victor-engmark/xkcd-passphrase-generator.git
cd xkcd-passphrase-generator
./generate.sh
l0b0
  • 51,350
0

In a nutshell:

random.sample(open("/usr/share/dict/words").readlines(), 4)

Produces e.g.:

['controverts\n', "Queensland's\n", 'plaids\n', 'aback\n']

Full code would be a bit longer, smth like,

import random; print("".join(random.sample(open("/usr/share/dict/words").readlines(), 4)))
Weyden
Geronimo's
Jidda
enumerate

On the upside, you occasionally get perls like this:

polyamory
replicates
unmarried
diseases

On the downside, you might get words that are hard to spell or even type:

fezzes
Lumière's
tercentenary's
Liliuokalani

And of course, if you were to pick one output out of the several options, you narrow down the search space dramatically, so you have to stick to first (i.e. truly random) set of words.

  • As @meeDamian pointed out, PRNG source must be secure, and also PRNG algorithm must be secure, thus random is a bit of a misfit. –  Sep 24 '18 at 01:57
0

xkcdpass is a simple way to get this without writing one-liners.

$ xkcdpass 
optimal mycology Kumamoto thorny chrism unsavoury

There are options to set number of words, etc.

To install:

  • Linux/Unix/macOS: pip install xkcdpass (you may need to sudo).
  • If you don't have Python / pip, look for packages like python and python-pip, for Python 2 or 3.
RichVel
  • 199