Generating Good Passwords, Part II

Passwords. They're the bane of computer users and a necessary evil, but they have risks and challenges associated with them. None of the choices are great. If it's up to your memory, you'll end up using the same password again and again. Use a password manager like 1Password, and you're reliant on its database security and portability. Two-factor? Um, can I borrow your phone for a minute?

Still, having complex and random passwords is definitely more secure than having a favorite phrase or variation you've been using for years. You know what I mean, just own it; you've been using the same PIN and password forever, right?

Last time, I built a script that could produce a random character from one of a set of character sets. For example, a random uppercase letter can be produced like this:


${uppercase:$(( $RANDOM % ${#uppercase} )):1}

Add lowercase and a constrained set of punctuation and some rules on how many of each you want, and you can make some pretty complicated passwords. To start, let's just focus on a random sequence of n uppercase letters.

That's easily done:

while [ ${#password} -lt $length ] ; do
   letter=${uppers:$(( $RANDOM % ${#uppers} )):1}

Remember that the ${#var} notation produces the length of the current value of that variable, so this is an easy way to build up the $password variable until it's equal to the target length as specified in $length.

Here's a quick test run or two:

$ sh
password generated = HDBYPMVETY
password generated = EQKIQRCCZT
password generated = DNCJMMXNHM

Looks great! Now the bigger challenge is to pick randomly from a set of choices. There are a couple ways to do it, but let's use a case statement, like this:

while [ ${#password} -lt $length ] ; do
  case $(( $RANDOM % 4 )) in
     0 ) letter=${uppers:$(( $RANDOM % ${#uppers} )):1}  ;;
     1 ) letter=${lowers:$(( $RANDOM % ${#lowers} )):1}  ;;
     2 ) letter=${punct:$((  $RANDOM % ${#punct}  )):1}  ;;
     3 ) letter=${digits:$(( $RANDOM % ${#digits} )):1}  ;;

Since you're basically weighing upper, lower, digits and punctuation the same, it's not a huge surprise that the resultant passwords are rather punctuation-heavy:

$ sh
password generated = 8t&4n=&b(B
password generated = 5=B]9?CEqQ
password generated = |1O|*;%&A;

These are all great passwords, impossible to guess algorithmically (and, yeah, hard to remember too, but that's an inevitable side effect of this kind of password algorithm).

But let's say that you'd rather have it be biased toward letters and digits than punctuation, because it's so much easier to type. That can be done by simply expanding the random number choice and assigning more than one value to those options you want to have appear more frequently, like this:

while [ ${#password} -lt $length ] ; do
   case $(( $RANDOM % 7 )) in
     0|1 ) letter=${uppers:$(( $RANDOM % ${#uppers})):1} ;;
     2|3 ) letter=${lowers:$(( $RANDOM % ${#lowers})):1} ;;
     4|5 ) letter=${punct:$(( $RANDOM % ${#punct}  )):1} ;;
     6 ) letter=${digits:$(( $RANDOM % ${#digits}  )):1} ;;

This works better, and the results are a bit less like a cat running across your keyboard:

$ sh
password generated = /rt?7D8QxR
password generated = us&*gpyB*-
password generated = rB}?2:)eJM
password generated = PC34jOD_}2

Next time, maybe I'll switch things around and let the user specify desired length and probability of punctuation being added to the password produced. Stay secure until then!

Dave Taylor has been hacking shell scripts on UNIX and Linux systems for a really long time. He's the author of Learning Unix for Mac OS X and Wicked Cool Shell Scripts. You can find him on Twitter as @DaveTaylor, and you can reach him through his tech Q&A site: Ask Dave Taylor.

Load Disqus comments