There are many ways to generate passwords, and to avoid passwords in the first place (which you really should do), but I wanted a little practice in writing interactive bash scripts. So I chose to create a password generator based on the Diceware algorithm, even though I don’t carry any dice with me.
Find of the day is the excellent gum tool that makes it easy to create pretty user interaction. Thanks Ari!
#!/bin/bash
set -eo pipefail
# Ref: https://theworld.com/~reinhold/diceware.html
# Ref: https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases
if [[ ! -x "$(command -v curl)" ]]; then
echo "Please install curl"
exit 1
fi
if [[ ! -x "$(command -v shuf)" ]]; then
echo "Please install coreutils"
exit 1
fi
if [[ ! -x "$(command -v gum)" ]]; then
echo "Please install https://github.com/charmbracelet/gum"
exit 1
fi
opt_five='five dice, long wordlist'
url_five='https://www.eff.org/files/2016/07/18/eff_large_wordlist.txt'
dst_five='eff_large_wordlist.txt'
opt_four_short='four dice, short wordlist'
url_four_short='https://www.eff.org/files/2016/09/08/eff_short_wordlist_1.txt'
dst_four_short='eff_short_wordlist_1.txt'
opt_four_long='four dice, long wordlist'
url_four_long='https://www.eff.org/files/2016/09/08/eff_short_wordlist_2_0.txt'
dst_four_long='eff_short_wordlist_2_0.txt'
echo 'Which wordlist to use?'
WORDLIST=$(gum choose "$opt_five" "$opt_four_short" "$opt_four_long")
echo ">> $WORDLIST"
echo 'How many words to pick?'
COUNT=$(gum input --value='4')
echo ">> $COUNT"
echo 'Which word separator to use?'
SEPARATOR=$(gum choose 'dash' 'space' 'underscore')
echo ">> $SEPARATOR"
CACHE_DIR="${HOME}/.diceware"
if [[ ! -d "$CACHE_DIR" ]]; then
mkdir "$CACHE_DIR"
fi
word_src="$url_five"
word_file="${dst_five}"
if [[ "$WORDLIST" == "$opt_four_short" ]]; then
word_src="$url_four_short"
word_file="$dst_four_short"
fi
if [[ "$WORDLIST" == "$opt_four_long" ]]; then
word_src="$url_four_long"
word_file="$dst_four_long"
fi
word_file="${CACHE_DIR}/${word_file}"
if [[ ! -f "$word_file" ]]; then
curl -sSf "$word_src" -o "$word_file"
fi
dice='4'
if [[ "$WORDLIST" == "$opt_five" ]]; then
dice='5'
fi
separator=' '
if [[ "$SEPARATOR" == "dash" ]]; then
separator='-'
elif [[ "$SEPARATOR" == "underscore" ]]; then
separator='_'
fi
phrase=''
for i in $(seq "$COUNT"); do
group=''
for j in $(seq "$dice"); do
roll=$(shuf -i 1-6 -n 1 --random-source=/dev/random)
if [[ -z "$group" ]]; then
group="$roll"
else
group="${group}${roll}"
fi
done
word=$(grep "$group" "$word_file" | awk '{print $2}')
if [[ -z "$phrase" ]]; then
phrase="$word"
else
phrase="${phrase}${separator}${word}"
fi
done
gum style \
--border double --align center \
--margin '1 2' --padding '2 4' \
"$phrase"
Sample output:
$> ./passphrase.sh
Which wordlist to use?
>> four dice, long wordlist
How many words to pick?
>> 4
Which word separator to use?
>> dash
╔═════════════════════════════════════════╗
║ ║
║ ║
║ jawbreaker-saffron-bullfrog-kiosk ║
║ ║
║ ║
╚═════════════════════════════════════════╝
Hope you find this useful.