Skip to content

Instantly share code, notes, and snippets.

@atoponce
Last active October 17, 2020 16:53
Show Gist options
  • Save atoponce/b542807f389d5af54b2102e7ccdffd95 to your computer and use it in GitHub Desktop.
Save atoponce/b542807f389d5af54b2102e7ccdffd95 to your computer and use it in GitHub Desktop.
Password generation in the shell

Simple Shell Password Generation

Just using the shell, either with built-in tools, or 3rd party generators, for building passwords with at least 70-bits of entropy (1 in at least 1,180,591,620,717,411,303,424 possibilities).

Each provide their own advantages and disadvantages.

Built-in Tools

All graphical keyboard characters

All possible 94 graphical characters (not the <Space> or <Tab>) are randomly chosen.

  • Length: 11 characters
  • Complexity: Very frustrating to type
  • Memorable: Difficult
$ tr -cd '[:graph:]' < /dev/urandom | fold -w 11 | head -n 1
3R=%MGPos}0

Alphanumeric

Lowercase, uppercase, and digits only. No special characters.

  • Length: 12 characters
  • Complexity: Frustrating to type on mobile
  • Memorable: Moderate
$ tr -cd 'a-zA-Z0-9' < /dev/urandom | fold -w 12 | head -n 1
mhD8TMCBFldd

Unambiguous (lowercase only)

Based on Crockford's base-32, which uses all 10 digits, then removes ambiguity by removing i, l, o, and u.

  • Length: 14 characters
  • Complexity: Easy to type
  • Memorable: Moderate, easy to read for vision difficulties
$ tr -cd '0-9a-hjkmnp-tv-z' < /dev/urandom | fold -w 14 | head -n 1
f2hp95rjxzd4az

XKCD-style

Based on the default /usr/share/dict/american-english word list, which on Debian and Ubuntu systems is ~100k words. If you have a word list of at least 262,144 words, you only need 4 words to build your passphrase.

  • Length: 5 words
  • Complexity: Easy to type
  • Memorable: Easy to recall
$ shuf --random-source=/dev/urandom -r /usr/share/dict/words | head -n 5 | paste -sd '-'
McQueen's-pithily-biases-push-propelling

UNIX-portable Shell Functions

These three functions can be added to any Bourne-compatible shell rc-config file (Bash, KSH, ZSH, dash, etc.) on many UNIX-like systems (GNU/Linux, *BSD, macOS, Solaris, etc.).

These functions are not POSIX, per the comments:

shuff() {
    # Tries to use a CSPRNG for shuffling
    # /dev/urandom and /dev/stdin are not POSIX
    if [ "$(command -v shuf)" ]; then
	shuf -r -n "$1" --random-source=/dev/urandom
    elif [ "$(command -v shuffle)" ]; then
	# NetBSD uses arc4random_uniform() in src/usr.bin/shuffle/shuffle.c
	shuffle -f /dev/stdin -p "$1"
    else
	awk 'BEGIN{
	    "od -tu4 -N4 -A n /dev/urandom" | getline
	    srand(0+$0)
	}
	{print rand()"\t"$0}' | sort -n | cut -f 2 | head -n "$1"
    fi
}
gen_monkey_pass() {
    # Generates an unambiguous password with at least 70 bits entropy 
    # Uses Crockford's base32
    # /dev/urandom is not POSIX
    i=0
    [ $(printf "$1" | grep -E '[0-9]+') ] && num="$1" || num="1"
    until [ "$i" -eq "$num" ]; do
	i=$((i+1))
	LC_ALL=C tr -cd '0-9a-hjkmnp-tv-z' < /dev/urandom | dd bs=1 count=14 2> /dev/null 
	echo # add newline
    done | column
}
gen_xkcd_pass() {
    # Generates a passphrase with at least 70 bits entropy
    # /dev/stdin is not POSIX
    i=0
    [ $(printf "$1" | grep -E '[0-9]+') ] && num="$1" || num="1"
    # Solaris, Illumos, FreeBSD, OpenBSD, NetBSD, GNU/Linux, OS X:
    [ $(uname) = "SunOS" ] && file="/usr/dict/words" || file="/usr/share/dict/words"
    dict=$(LC_ALL=C grep -E '^[a-zA-Z]{3,6}$' "$file")
    size=$(printf "$dict" | wc -l | sed -e 's/ //g')
    entropy=$(printf "l(${size})/l(2)\n" | bc -l)
    words=$(printf "(70+${entropy}-1)/${entropy}\n" | bc)
    until [ "$i" -eq "$num" ]; do
	i=$((i+1))
	printf "$dict" | shuff "$words" | paste -s -d '.' /dev/stdin
    done | column
}

After saving, execute a new shell. Now execute the functions, with an argument to the number of passwords you want:

$ gen_monkey_pass
qb8s2f5rwqmtyy

$ gen_monkey_pass 10
6x54198cg3p4rf	e5vxgndv498k5t	6paxks5r95feyt	k51mr2bxx0021r	98ytcybrjvf3xv
37ny17bchgyb52	n5cdn42pgnecf4	k9x6w5tmymjayb	sekfnj362vy027	x7z51d8hr5kesy

$ gen_xkcd_pass
Royce.mart.ached.Oslo.Sharpe

$ gen_xkcd_pass 10
gutsy.sump.parody.loofah.tout		clap.Serbia.poked.snaked.blithe
boldly.mower.boot.heists.realty		tortes.jam.yogi.list.cynic
free.nay.chides.Tanya.Crimea		Lucia.minty.Seljuk.havens.fiddle
Bret.retry.teen.untold.logons		poplar.clingy.flays.Vulcan.Styron
gig.runway.abut.Viking.Tucker		meted.catnip.recopy.madman.ulcer

Third-party Tools

Nodepassgen

Personal utility written by me in Nodejs. Initially, I wrote a web-based password generator at https://ae7.st/g/, and open-sourced it at https://github.com/atoponce/webpassgen. Someone asked for a command-line version, so as they did not have to trust the browser. Because the bulk of the code was already written in JavaScript, I migrated the code to Nodejs, and https://github.com/atoponce/nodepassgen was born.

Clone the Git repository first, then create a symlink in /usr/local/bin:

$ git clone https://github.com/atoponce/nodepassgen.git
$ sudo ln -s /path/to/nodepassgen/nodepassgen /usr/local/bin

Many password choices by default

Now execute it, hyphenating passphrases:

$ nodepassgen -H
Alternate(Trump): potential-1-Unified-she-Luis-Marco
Bitcoin(English): rail-scout-mammal-sniff-heavy-crowd-velvet
Diceware(English): coma-bonn-byers-lab-hairy-tapa
EFF(Short): usher-dawn-twine-aide-greet-most-sepia
Pseudowords(Bubble_Babble): xolac-rebix-vacam-fitus-rizox
Random(Base94): v7hiP5]|Hy=

JSON support

For parsing, JSON formatting is supported which is useful for scripts:

$ nodepassgen -H --json
[
  {
    "Generator": "Alternate",
    "Wordlist": "Trump",
    "Password": ".@VP-seven-RNC-@HighonHillcrest-Waterbury-fifty",
    "Characters": 42,
    "Entropy": 77
  },
  {
    "Generator": "Bitcoin",
    "Wordlist": "English",
    "Password": "potato-search-device-grid-bullet-disorder-festival",
    "Characters": 44,
    "Entropy": 77
  },
  {
    "Generator": "Diceware",
    "Wordlist": "English",
    "Password": "sir-ordain-fawn-timid-crux-shu",
    "Characters": 25,
    "Entropy": 77
  },
  {
    "Generator": "EFF",
    "Wordlist": "Short",
    "Password": "city-item-bulk-hunk-fable-volt-foe",
    "Characters": 28,
    "Entropy": 72
  },
  {
    "Generator": "Pseudowords",
    "Wordlist": "Bubble_Babble",
    "Password": "xaruf-lakuz-haguv-rizis-xavax",
    "Characters": 25,
    "Entropy": 78
  },
  {
    "Generator": "Random",
    "Wordlist": "Base94",
    "Password": "><8Ek8=s-1u",
    "Characters": 11,
    "Entropy": 72
  }
]

Check nodepassgen --help for options, such as changing the minimum entropy level, hyphenating the passphrases, changing the dictionaries, and other options.

Pwgen

The pwgen(1) utility is nice, even if its defaults are lacking. Even though it defaults to 160 passwords with no options passed, if placed in a shell script, where STDOUT is not a tty, only 1 password is generated.

First make sure you have it installed:

$ sudo apt install pwgen

Alphanumeric

Uppercase, lowercase, and digits only. No special characters.

  • Length: 12 characters
  • Complexity: Frustrating to type on mobile
  • Memorable: Moderate
$ pwgen -s 12 1
Oi6V0O9gwY38

Scripting passwords for users

This is where pwgen(1) really shines. If you have a list of users, this can be useful:

$ cat users.txt
user1
user2
user3
user4
user5

$ while read line; do pwgen -s 12 | sed "s/^/$line /"; done < users.txt
user1 Sk6yU47VsLl5
user2 wGef9E1QWWrm
user3 du9ucj11YPjY
user4 23PVw1ruiwhn
user5 lI2L0y09L33G

APG

I'm not really a fan of apg(1). I find it's options overly complex and the source code difficult to audit, especially with the "pronounceable" passwords. To keep things simple, meeting our minimum 70-bits entropy requirement, just copy/paste what's below.

First make sure you have it installed:

$ sudo apt install apg

All graphical keyboard characters

All possible 94 graphical characters (not the <Space> or <Tab>) are randomly chosen.

  • Length: 11 characters
  • Complexity: Very frustratirg to type
  • Memorable: Difficult
$ apg -a 1 -m 11 -n 1
RXqX[f280:.

Alphanumeric

Lowercase, uppercase, and digits only. No special characters.

  • Length: 12 characters
  • Complexity: Frustating to type on mobile
  • Memorable: Moderate
$ apg -a 1 -M NCL -m 12 -n 1
qOHZk2rJb7xI

Unambiguous (lowercase only)

Based on Crockford's base-32, which uses all 10 digits, then removes ambiguity by removing i, l, o, and u.

  • Length: 14 characters
  • Complexity: Easy to type
  • Memorable: Moderate, easy to read for vision difficulties
$ apg -a 1 -M LN -E iolu -m 14 -n 1
cdjvxh3x5sqppy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment