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="ABCDEFGHIJKLMNOPQRSTUVWXYZ" ${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} password="${password}$letter" done

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 makepw.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} ;; esac password="${password}$letter" done

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 makepw.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} ;; esac password="${password}$letter" done

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

$ sh makepw.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!