ColdFusion in Context: Password Generation

Suppose you need to assigning user passwords when an account is opened or when the user forgets a password. The password often has to meet certain criteria, and it should not contain vulgar language. Here is an approach to make your job easier. Along the way, you'll use ColdFusion's "random" number generator, you'll learn to avoid a problem with one of the functions associated with that generator, and you'll find a little-used string function to be quite helpful.

Test Components

Make a throwaway page - call it check.cfm - that takes ColdFusion's documentation at face value. The documentation says to use the randomize function prior to asking the "random" number generator for real output. The randRange function returns an integer between the two integers you specify.

<cfset dummy=randomize(5)>
<cfset mynr=randRange(1,10)>
<cfoutput>#mynr#</cfoutput>

If your box acts the way mine does, this causes the same number to appear each time: nice for testing but a problem for operation. Now remove the randomize statement and rename the page check2.cfm. It will act the way you want it to: generating a "random" number from 1 to 10.

<cfset mynr=randRange(1,10)>
<cfoutput>#mynr#</cfoutput>

Pick Criteria

Consider the help desk environment for a moment. At the help desk, you can't be sure which office or user is speaking with you on the phone. You have a little more assurance if you place the call, but usually, the user calls you and could be anybody. Therefore, you aren't going to give out passwords over the phone. You'll probably provide them in writing, typically via E-mail.

Because the password will be written rather than spoken, the emphasis shouldn't be on avoiding similar sounds but on avoiding characters that might be confused in written form. You won't need to generate pronounceable passwords; you want to be sure your passwords that aren't visually confusing.

Therefore, you should avoid certain letters. In certain fonts, capital "I" looks like small "l" (el) which in turn looks like the number 1 (one). Similarly, capital "O" looks like the number 0 (zero). I've seen users get confused by these characters and recommend you avoid them.

There's another reason you can't just generate passwords from the alphabet: profanity. Imagine your user's reaction if your automated system swears at the user.

There's a third reason you can't just work from the alphabet. Security experts tell you to use numbers, special characters, and mixed case letters that don't spell anything.

It is possible, but it's a pain to reject passwords that contain dictionary words embedded in them. However, there's a simpler approach: don't use any vowels. The combination of numbers, mixed case consonants, and special characters produces a good temporary password that will suffice until the user can replace it with something meaningful. In fact, the sheer perversity of the password will probably encourage the user to change it right away. This is good.

Finally, to avoid hard-to-find failures down the road, omit characters that have special meanings or which are hard to distinguish from each other. I omitted the backslash, pipe, single and double quotes, the back quote, pound sign, ampersand, greater-than sign, less-than sign, and the space. (Spaces don't work well for ftp and might get trimmed by accident if on either end of the password). If you find that other characters cause problems (with your database engine perhaps), omit them as well or plan to handle them carefully.

Set the Stage

Bearing these criteria in mind, you have four groups of characters that must be represented: special characters, numbers, lower-case consonants, and upper-case consonants (obtained by shifting the list of lower-case consonants). Concatenate the groups and note the length of the overall string so you can pick "random" characters from it. However, to permit you to reject passwords later on that don't have a character from every group, keep the groups stored separately as well. Put all the remaining code for this demonstration in passgen.cfm.

This password is suitable for one-time use.  The letters I, L, and O are not used.<br>
Change it to something else after you use it to log on.
<p>
<cfset Odd="!@$%^*()_+[]{}~-=;:,./?">
<cfset Num="1234567890">
<cfset Con="qwrtyupsdfghjkzxcvbnm">
<cfset Pool=Odd&Num&Con&ucase(Con)>
<cfset Poolsize=len(Pool)>

Loop the Loop

Because your selection is random (you hope), it will often happen that not every group gets represented. Therefore, you need an outer loop that tries again until a good password is generated and an inner loop that picks a character for each position in the password. Start the password off empty, pick a random length from 9 to 12 characters (in this example), and then randomly add characters from the pool until the password is the desired length.

Once you've done this, the outer loop has to perform the test. The findOneOf function is a good one for this situation. If any of the characters in the first string is in the second, it returns true. Thus, the test to be sure each of the four groups is represented by at least one character is a simple one. If the test succeeds, Done is set to true and the outer loop ends. If not, the outer loop resets the password and the inner loop tries again. (If you're coding from scratch, add a counter test to the condition and increment the counter while looping; this keeps you from being very unhappy if your code is somehow flawed.)

<cfset Done=0>
<cfloop condition="not Done">
<cfset Newpass="">
<cfset Passlen=randrange(9,12)>
<cfloop index="dummy" from="1" to="#Passlen#">
<cfset Char=mid(Pool,randrange(1,Poolsize),1)>
<cfset Newpass=Newpass&Char>
</cfloop>
<cfif findOneOf(Odd,Newpass) and findOneOf(Num,Newpass)
and findOneOf(Con,Newpass) and findOneOf(ucase(Con),Newpass)>
  <cfset Done=1>
</cfif>
</cfloop>
<cfoutput>#Newpass#</cfoutput>

Try it Out

Browse passgen.cfm and see if the result meets your needs. Modify it as desired, and incorporate it into your overall application. (Imagine if users who have lost their password could ask your application to send a fresh one to the E-mail address in their account profile; this would reduce your trouble call volume significantly.) Get on the Internet to see what others have done in this area. (For example, there's an especially nice generator that provides a memory aid with each password it generates.) Enjoy, and share the wealth. =Marty=