A Poor Man's Random Password Generator

apg is the "Automated Password Generator," a piece of software to generate random passwords. Perhaps not widely known, but undoubtedly used by a lot of systems administrators. I wrote a replacement Bash script so that I could be guaranteed of having an equivalent installed as part of our local Ansible repository rather than relying on other Ansible users to know to install a relatively obscure piece of software that may not even be available for Mac (I know it's widely available on Linux). I also like that I know exactly how this one works: it's simple, and even if you didn't write it you can read and understand the code.

Please email me if you believe there's something wrong with this from a security perspective.

Here's how it works: /dev/urandom spews out a steady stream of random bytes. Not all of these fit in the English alphabet, so we filter out characters that don't match, and then crop the remainder to the desired password length.

This is, of course, more useful in the age of password managers and/or browsers that memorize your password.

(This script has a strong English alphabet bias.)

#!/bin/bash
#
#   Filename:      apggo
#   Purpose:  Poor-man's 'apg' - a script to generate random passwords.

MINLENGTH=8
MAXLENGTH=100
SIMPLE='A-Za-z0-9'
NONSIMPLE='A-Za-z0-9!@$%^&*(){}[]=+-_/?\|~`'

function help() {
    echo "Usage: $(basename "${0}") [-h] [-s] [-l <len>]"
    echo "   Generates a pseudo-random password of length <len>."
    echo "   For WORD-BASED PASSWORDS, see 'randwords' script."
    echo ""
    echo "   '-l' for length, requires an integer value."
    echo "   '-h' for help."
    echo "   '-s' is 'simple' passwords without special characters."
}

function passgen() {
    length=${1}
    if (( length < MINLENGTH ))
    then
        echo "Password minimum length of ${MINLENGTH} enforced."
        exit 4
    fi

    if (( length > MAXLENGTH ))
    then
        echo "Password maximum length of ${MAXLENGTH} enforced."
        exit 4
    fi
    # These settings aren't needed on Linux but often are on Mac:
    export LC_CTYPE=C
    export LC_ALL=C
    # The 'tr' string below represents all ALLOWED characters.
    dd if=/dev/urandom bs=1 count=$(( 8 * length )) 2>/dev/null \
        | tr -dc "${SUBS}" \
        | head -c "${length}"
    echo
}

SUBS="${NONSIMPLE}"

while getopts :hl:s opt ; do
    case $opt in
        h)  help
            exit 0
            ;;

        l)  # Check we have an integer (
            # https://stackoverflow.com/questions/2210349/test-whether-string-is-a-valid-integer#18620446
            # )
            count="$OPTARG"
            if ! [[ ${count} =~ ^-?[0-9]+$ ]]
            then
                echo "Parameter must be an integer."
                help
                exit 2
            fi
            ;;
        s)  SUBS="${SIMPLE}"
            ;;
        :)  echo "Option -$OPTARG requires an argument." >&2
            help
            exit 1
            ;;
        *)  echo "unknown option ... "
            help
            exit 1
            ;;
    esac
done

if [ -n "${count}" ]
then
    passgen "${count}"
else
    echo "Please set a value for password length."
    help
    exit 1
fi

Bibliography