There are a few "XKCD password" generators out there, but the task seems so straight forward, so I wanted a compact and simple solution. My requirements were:

  • One-line alias (or few-line function) to put in .profile
  • Standard Unix commands (require no packages)
  • Column output (like pwgen)
  • Titlecase words (for readability)
  • True randomness

(I'm happy to renounce command line options in exchange for simplicity.)

for i in {1..32}; do grep -E "^[a-z]{4,8}$" /usr/share/dict/words | gshuf -n4 | gsed 's/.*/\u&/' | tr -d '\n' | awk '{print $1}'; done | column

Current problems:

  • shuf requires brew install coreutils (since I'm on macOS).
  • sed requires brew install gnu-sed.
  • tr -d '\n' | awk '{print $1}' seems silly.
  • shuf: Is it truly random?

How can I make a short, yet readable the command line that works on "all" Unix flavours?

PS! I also found that by using SCOWL, which has a range from short word lists with common names to long word lists with obscure words, you can trade-off memorability Vs. entropy.


shuf: Is it truly random?

No. According to the GNU documentation,

By default these commands use an internal pseudo-random generator initialized by a small amount of entropy, but can be directed to use an external source with the --random-source=file option.

For password generation you'll want to use --random-source=/dev/random.

gsed 's/.*/\u&/'

I've not seen this before. Nice trick, but it can be done in pure bash. The catch is that bash case modification is part of parameter expansion, so you have to store each word in a variable, and you'll need a loop. If you're not completely committed to having a one-liner then this is something which could be pulled out as an auxiliary function, and you might even find it useful elsewhere.

tr -d '\n' | awk '{print $1}'

awk seems a bit heavyweight given that the only reason for calling it is to append a newline. I'd replace with

tr -d '\n'; echo

In order to speed things up at the cost* of a small reduction in entropy, you could try to rewrite so that you only call shuf once, getting 128 words, and then split them into fours.

* (There's room for debate as to whether it's a cost or a benefit when you see whether using /dev/random slows things down while it tries to refill its entropy pool).

| improve this answer | |

In gsed 's/.*/\u&/', you can drop the *, because \u affects only the first character, so gsed 's/./\u&/' is equivalent.

The tr -d '\n' | awk '{print $1}' is indeed a bit silly, but acceptable. The alternative proposed by @PeterTaylor is better though. Another alternative without using additional processes:

p=$(grep -E "^[a-z]{4,8}$" /usr/share/dict/words | gshuf -n4 | gsed 's/./\u&/'); echo ${p//[^a-zA-Z]/}

(This is to replace the body of the for loop.)

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy