Humans are not very good at generating random passwords, so it can be helpful to use a proven computer algorithm and then simply work to memorize the password that was generated. We are going to use a bit of Bash scripting with the OS pseudo-random number generator and some basic Unix utilities for this. One of the best ways to memorize a strong, random password is to practice typing it. To help with this, we are also going to use a bit of Bash scripting that will let us type the password repeatedly and check if it is correct.
We could create three different shell scripts for this and keep them somewhere like
/usr/local/bin
(for system-wide use) or somewhere in your home directory (for personal use). Or, we could define them as functions in /etc/bash.bashrc
(for system-wide use) or ~/.bashrc
(for personal use). I’ll present them both ways, first as individual scripts and at the end as a series of functions. Putting them in separate script files would make them available from other shells, etc. (If you login to csh and invoke one of the scripts it will simply call Bash to run it. If you defined them as functions they would be unavailable in csh.)First, here is a one-liner to generate a random password:
cat /dev/urandom | tr -cd "[:graph:]" | head -c 13 && echo
The first part reads from the pseudorandom number generator and passes it to the next part, the tr command removes all characters that are not printable ascii (you could also use
"[:alnum:]"
to generate an alphanumeric password), head -c
takes only the specified number of characters and then terminates the pipeline, and the echo
command simply outputs a newline, so that we don't end up with the command prompt being printed on the same line at the end of the password.Now, we’ll look at a more complete example that takes command line options instead of manually editing our command:
#!/bin/bash
chars="[:graph:]"
length="13"
forbidden=""
for i in $(seq 1 $#); do
if [[ "${!i}" == "--alnum" ]]; then
chars="[:alnum:]"
elif [[ "${!i}" == "--length" ]]; then
((n=$i+1))
length="${!n}"
elif [[ "${!i}" == "--forbidden" ]]; then
((n=$i+1))
forbidden="${!n}"
fi
done
cat /dev/urandom | tr -cd "$chars" | tr -d "$forbidden" | head -c "$length"
echo
This script takes several arguments. The
--alnum
argument limits the password to alphanumeric characters rather than printable ascii. The --length
option is followed by the number of characters to generate and --forbidden
is an additional list of forbidden characters (useful for sites that accept special characters with a few stated exceptions). The default is 13 characters consisting of printable ascii characters. 13 random ascii characters meets the NIST recommendation for 80 bits of entropy for a strong password (learn more about password strength on Wikipedia).The
for
loop here counts the number of arguments passed to the script (stored in $#
) and loops over them. The ${!var}
notation treats $var
as the name of another variable. In other words, if $i
is 1
, then ${!i}
is the same as $1
which is the first argument that was passed to the script. The double parentheses are used to evaluate a mathematical expression. After evaluating the command line arguments, we have essentially the same pipeline we used before. The -d
option for tr
deletes characters from the input, while -c
means to delete everything but the specified characters (the “complement” of the specified character set). So the first tr
command removes all of the characters except for printable ascii (or alphanumeric, if specified), the second removes additional characters specified with --forbidden
.Now, on to our password practicing tools. First, we need a way to set the password:
#!/bin/bash
name=${1:-default}
read pw
echo -ne "\033[1A\033[0K"
echo -n $pw | sha512sum | tr -d ' -' > ~/.${name}pwhash
The
read
command takes input from the user and stores it in a variable named pw
. By default, read
prints what you are typing to the terminal. We allow it to do so here, so that you can make certain you are typing the password correctly the first time. However, as soon as we have finished typing and hit “enter”, we clear that line so the password is no longer visible. The -n
option tells echo
not to automatically output a newline at the end, and the -e
tells it to interpret escape sequences. The sequence \033[1A
moves the cursor up one line, and \033[0K
deletes the current line. Rather than storing the password itself, we store a hash of the password for a bit of extra security (hopefully, of course, the machine we are doing this on is already secure, but this is a simple precaution to take). The sha512sum
prints a couple of spaces and a hyphen at the end; the tr -d ' -'
removes these. This script optionally takes one argument, a name so that you set and practice multiple passwords. The notation ${1:-default}
is equivalent to $1
if it is set, otherwise it defaults to default
.Now, we need a way to practice typing the password we set:
#!/bin/bash
name=${1:-default}
read -s pw
userhash=$(echo -n $pw | sha512sum | tr -d ' -')
storedhash=$(cat ~/.${name}pwhash)
if [[ $userhash == $storedhash ]]; then
echo "Correct"
else
echo -e "Wrong\a"
fi
This time we used the
-s
option so that read does not print what you are typing to the terminal. Similar to the first script, this one optionally allows you to specify a name and then compares the hash of the password you type to the one that was previously stored. If they match, it informs you that you have typed the password correctly; if not, it let’s you know it was wrong. The \a is the bell character; it may give an audible alert, or in some cases a visual alert or nothing at all, but it is a nice touch to get your attention when the password is typed incorrectly.Putting them all into functions is quite simple:
function genpw() {
chars="[:graph:]"
length="13"
forbidden=""
for i in $(seq 1 $#); do
if [[ "${!i}" == "--alnum" ]]; then
chars="[:alnum:]"
elif [[ "${!i}" == "--length" ]]; then
((n=$i+1))
length="${!n}"
elif [[ "${!i}" == "--forbidden" ]]; then
((n=$i+1))
forbidden="${!n}"
fi
done
cat /dev/urandom | tr -cd "$chars" | tr -d "$forbidden" | head -c "$length"
echo
}
function setpw() {
name=${1:-default}
read pw
echo -ne "\033[1A\033[0K"
echo -n $pw | sha512sum > ~/.${name}pwhash
}
function ppw() {
name=${1:-default}
read -s pw
userhash=$(echo -n $pw | sha512sum)
storedhash=$(cat ~/.${name}pwhash)
if [[ $userhash == $storedhash ]]; then
echo "Correct"
else
echo -e "Wrong\a"
fi
}
function unsetpw() {
name={1:-default}
shred -uxn1 ~/.${name}pwhash
}
I added an extra one here to unset the password by removing the hash from your system, although this one is fairly trivial. In addition to learning one handy way to generate and learn strong random passwords, hopefully this little exercise has also given us a look at some handy Unix tools and Bash scripting features. For comparison, I’ve also written a Python version of these scripts.
No comments:
Post a Comment