Generating Random Passwords In ColdFusion Based On Sets Of Valid Characters
Richard White over on CF-Talk asked about generating random passwords in ColdFusion based on his particular business rules. These rules included:
- Must be exactly 8 characters in length
- Must have at least 1 number
- Must have at least 1 uppercase letter
- Must have at least 1 lower case letter
Tom Chiverton suggested using ASCII values and randomization, which is a great suggestion. In fact, it is one that I have used myself many times in the past. However, I created this demo based on explicitly defined sets of character data as I feel that it has some benefits; it is more readable and allows for more flexibility in what the character sets are (just my opinion).
That being said, here is the algorithm I came up with in ColdFusion to generate the random passwords:
<!---
We have to start out be defining what the sets of valid
character data are. While this might not look elegant,
notice that it gives a LOT of power over what the sets
are without writing a whole lot of code or "condition"
statements.
--->
<!--- Set up available lower case values. --->
<cfset strLowerCaseAlpha = "abcdefghijklmnopqrstuvwxyz" />
<!---
Set up available upper case values. In this instance, we
want the upper case to correspond to the lower case, so
we are leveraging that character set.
--->
<cfset strUpperCaseAlpha = UCase( strLowerCaseAlpha ) />
<!--- Set up available numbers. --->
<cfset strNumbers = "0123456789" />
<!--- Set up additional valid password chars. --->
<cfset strOtherChars = "~!@##$%^&*" />
<!---
When selecting random value, we want to be able to easily
choose from the entire set. To this effect, we are going
to concatenate all the previous valid character sets.
--->
<cfset strAllValidChars = (
strLowerCaseAlpha &
strUpperCaseAlpha &
strNumbers &
strOtherChars
) />
<!---
Create an array to contain the password ( think of a
string as an array of character).
--->
<cfset arrPassword = ArrayNew( 1 ) />
<!---
When creating a password, there are certain rules that we
need to follow (as deemed by the business logic). That is,
the password must:
- must be exactly 8 characters in length
- must have at least 1 number
- must have at least 1 uppercase letter
- must have at least 1 lower case letter
--->
<!--- Select the random number from our number set. --->
<cfset arrPassword[ 1 ] = Mid(
strNumbers,
RandRange( 1, Len( strNumbers ) ),
1
) />
<!--- Select the random letter from our lower case set. --->
<cfset arrPassword[ 2 ] = Mid(
strLowerCaseAlpha,
RandRange( 1, Len( strLowerCaseAlpha ) ),
1
) />
<!--- Select the random letter from our upper case set. --->
<cfset arrPassword[ 3 ] = Mid(
strUpperCaseAlpha,
RandRange( 1, Len( strUpperCaseAlpha ) ),
1
) />
<!---
ASSERT: At this time, we have satisfied the character
requirements of the password, but NOT the length
requirement. In order to do that, we must add more
random characters to make up a proper length.
--->
<!--- Create rest of the password. --->
<cfloop
index="intChar"
from="#(ArrayLen( arrPassword ) + 1)#"
to="8"
step="1">
<!---
Pick random value. For this character, we can choose
from the entire set of valid characters.
--->
<cfset arrPassword[ intChar ] = Mid(
strAllValidChars,
RandRange( 1, Len( strAllValidChars ) ),
1
) />
</cfloop>
<!---
Now, we have an array that has the proper number of
characters and fits the business rules. But, we don't
always want the first three characters to be of the
same order (by type). Therefore, let's use the Java
Collections utility class to shuffle this array into
a "random" order.
If you are not comfortable using the Java class, you
can create your own shuffle algorithm.
--->
<cfset CreateObject( "java", "java.util.Collections" ).Shuffle(
arrPassword
) />
<!---
We now have a randomly shuffled array. Now, we just need
to join all the characters into a single string. We can
do this by converting the array to a list and then just
providing no delimiters (empty string delimiter).
--->
<cfset strPassword = ArrayToList(
arrPassword,
""
) />
I ran that a bunch of times and here is the list of passwords it created:
RQ5nYqR1
QL3!g8TD
*dZWd5rP
I5Jvna!5
MqG%T38b
IgD1BLq^
!~51gpZC
As you can see, it is quite random (at least for my powers of perception) and each one complies with the business rules laid out. Of course, if your business rules change, you would have to update the algorithm as needed (and anyone let me know if they want to see a demo of that (based on their business rules)).
Want to use code from this post? Check out the license.
Reader Comments
Not that it matters but you should remove the letters "o", "i" and "s" from the strLowerCaseAlpha variable so the end user won't confuse their uppercase values with 0, 1 and 5.
Sam,
While I am not sure I am completely on board with that decision (but I do completely understand it), your comment goes quite nicely with the idea behind using character sets rather than spans of ASCII values. By using a set of characters, it makes it so easy to pick and choose which characters don't make the cut.
Nicely done.
I'd second Sam's comment but also include the lower case L from exclusion as on some fonts that looks the same as a 1 or capital I, not that I've ever confused them before ;)
When I first responded, I forgot that we were GENERATING passwords and was thinking just about passwords in general, which is why I was hesitant to get on board with the character restricitions. But now that I remember we are dealing with generating (hey, I have a lot on my mind), I totally agree. No need to confuse the end user.
Thanks a ton for your code. I was looking to create a cfscript function to do this so I did the simple mods on what you wrote to make it a function. Here is the code I created from the code in the original comment.
<cfscript>
/** generatePassword - this function will produce a randomly genereated password
* that is 8 characters long and includes at least one number, lower case letter,
* and upper case letter
*
* none cfscript code found at www.bennadel.com/blog/488-Generating-Random-Passwords-In-ColdFusion-Based-On-Sets-Of-Valid-Characters.htm
* adapted to cfscript code by Andy Marks
*
* @return the generated password
*/
function generatePassword()
{
var strLowerCaseAlpha = "abcdefghijklmnopqrstuvwxyz";
var strUpperCaseAlpha = UCase( strLowerCaseAlpha );
var strNumbers = "0123456789";
var strOtherChars = "!@##$%&*";
var strAllValidChars = (strLowerCaseAlpha & strUpperCaseAlpha & strNumbers & strOtherChars);
var arrPassword = ArrayNew(1);
var strPassword = "";
var intChar = 0;
arrPassword[ 1 ] = Mid(strNumbers,RandRange( 1, Len( strNumbers ) ),1);
arrPassword[ 2 ] = Mid(strLowerCaseAlpha,RandRange( 1, Len( strLowerCaseAlpha ) ),1);
arrPassword[ 3 ] = Mid(strUpperCaseAlpha,RandRange( 1, Len( strUpperCaseAlpha ) ),1);
for(intChar = ArrayLen(arrPassword) + 1; intChar LTE 8; intChar = intChar + 1)
{
arrPassword[ intChar ] = Mid(strAllValidChars,RandRange( 1, Len( strAllValidChars ) ),1);
}
CreateObject( "java", "java.util.Collections" ).Shuffle(arrPassword);
strPassword = ArrayToList(arrPassword,"");
return strPassword;
}
</cfscript>
Looks good dude. The only modification I would suggest is to pass in a password length (gt 4) to the function. Right now, you have hard coded 8. It might be a nice little dynamic feature. But looks good.
Freakin' awesome dude! Love it and using it! You helped a bunch!
Player. Glad to help. Hit me up if you get stuck anywhere.
Thanks man - just needed this for a quick data conversion project. Worked like a charm (once I got rid of the white space which was hurting my eyes :-))
Thanks!
@Peter,
Ha ha, you love the white space, you know it.
Ben, I used this today and it saved me a ton of time, I can't give you enough props or thank you enough for your entire site.
@Matt,
Thanks a lot! I really appreciate hearing that.
You just saved me a ton of time and research, I just love how when I need to find something your blog usually has the answer, thanks for your continual contribution to the community!
@Jim,
Absolutely my pleasure! I'm so happy that posts like this continue to deliver value :)
Nice looking piece of code (as usual) - you just saved me some time!
@Holly,
Always happy to help out :)
Thanks! You saved my some time for sure.
Thanks Ben, I was starting to write my own and decided to check out your site (once again) since you are the CF King!
So many times your site has helped me with my projects.
I owe you a few brews if you are ever in upstate NY!
@Andy,
Ha ha, always glad to help! Where in NY do you live (I'm in NYC).
@Ben,
Elmira, Corning area (lots of micro-brews and loads of finger lakes wine!)
@Andy,
Ah, pretty far out west (relatively speaking).
Ben, even 4+ years after your original post this is still coming in handy! You are always a great source for solutions or at least a fresh perspective when trying to solve a problem. Thanks again! Keep up the good work!
Andy Marks, and Ben, this cfscript came in really handy. Thanks for writing it.
Eric
cool! There've been many times when I have wanted to do something like this, but as usual am coming across the post late. I can agree with the confusing users thing. When I first read the thing about the I, I forgot about fonts, and I thought O's where the only necessary thing...since I have confused them myself even in just coding (what is worse is that the keys on the board are so close to each other, it is easy to hit the wrong one without realizing it). In the font I usually use, the I has definite lines on top and bottom, and the 1 has a line at the top, but only on one side, slanting down, and of course has the bottom one, so they are pretty distinctive, but I could see how in some fonts they probably look very similar. Same with the lowercase l.
Just a quick note...if you are calling this file like I am, it might help to put this at the top of the file:
<cfprocessingdirective suppresswhitespace="yes">
<cfsetting enablecfoutputonly="no">
<cfsetting showdebugoutput="no">
...and this at the bottom
<cfoutput>#strPassword#</cfoutput></cfprocessingdirective><cfabort>
...that way you just get the password back. I'm using ajax to call it and then populate a form box with the generated password and I'm working on a dev box with all the debug turned on. The code I put in here keeps it returning just the generated password.
@David
I usually just use
<cfcontent type="application/json" reset="true"><cfoutput>#myvar#</cfoutput><cfabort>
replacing application/json based on the kind of data i'm returning. for example, text/plain text/html or text/xml etc.
Ben, despite this being 4 years old now it's still great. It's taken me a matter of minutes to add this to a site I'm working on currently - many many thanks. Your blog is always outstanding! Big love from England
I wish I scrolled down to see the cfscript version before I did this!
<cfcomponent displayname="genPassword" output="true" hint="generates password">
<cffunction name="genPassword" output="true" access="remote">
<cfargument name="min_length" default="20">
<cfargument name="qtyNumbers" default="1">
<cfargument name="qtyLowerCaseAlpha" default="1">
<cfargument name="qtyUpperCaseAlpha" default="1">
<cfargument name="qtyOtherChars" default="1">
<!--- Set up strings to build the pw from --->
<cfset strLowerCaseAlpha = "abcdefghjkmnpqrstuvwxyz" />
<cfset strUpperCaseAlpha = UCase( strLowerCaseAlpha ) />
<cfset strNumbers = "23456789" />
<cfset strOtherChars = "~!@$%^&*{}:;,.?/" />
<cfset strAllValidChars = (
strLowerCaseAlpha &
strUpperCaseAlpha &
strNumbers &
strOtherChars
) />
<cfset arrPassword = ArrayNew( 1 ) />
<cfset arrPassword=genPasswordPart(strNumbers, arguments.qtyNumbers, arrPassword)>
<cfset arrPassword=genPasswordPart(strLowerCaseAlpha, arguments.qtyLowerCaseAlpha, arrPassword)>
<cfset arrPassword=genPasswordPart(strUpperCaseAlpha, arguments.qtyUpperCaseAlpha, arrPassword)>
<cfset arrPassword=genPasswordPart(strOtherChars, arguments.qtyOtherChars, arrPassword)>
<!--- pad the rest of the password. --->
<cfloop
index="intChar"
from="#(ArrayLen( arrPassword ) + 1)#"
to="#arguments.min_length#"
step="1">
<cfset arrPassword[ intChar ] = Mid(
strLowerCaseAlpha,
RandRange( 1, Len( strLowerCaseAlpha ) ),
1
) />
</cfloop>
<!--- shuffle --->
<cfset CreateObject( "java", "java.util.Collections" ).Shuffle(
arrPassword
) />
<!--- Array to list, makes it a string--->
<cfset strPassword = ArrayToList(
arrPassword,
""
) />
<cfreturn strPassword>
</cffunction>
<cffunction name="genPasswordPart" >
<cfargument name="stringType" required="true">
<cfargument name="quantity" required="true">
<cfargument name="passwordArray" required="true">
<cfif arguments.quantity gt 0>
<cfloop from="1" to="#arguments.quantity#" index="i">
<cfset arguments.passwordArray[ arrayLen(arguments.passwordArray)+1 ] = Mid(
arguments.stringType,
RandRange( 1, Len( arguments.stringType ) )
,1
) />
</cfloop>
</cfif>
<cfreturn arguments.passwordArray>
</cffunction>
</cfcomponent>
Rev 2.0 of this, Ben, can you remove the prior post?
<cfcomponent displayname="genPassword" output="true" hint="generates password">
<cffunction name="genPassword" output="true" access="remote">
<cfargument name="min_length" default="20">
<cfargument name="qtyNumbers" default="1">
<cfargument name="qtyLowerCaseAlpha" default="1">
<cfargument name="qtyUpperCaseAlpha" default="1">
<cfargument name="qtyOtherChars" default="1">
<!--- Set up strings to build the pw from --->
<cfset strLowerCaseAlpha = "abcdefghjkmnpqrstuvwxyz" />
<cfset strUpperCaseAlpha = UCase( strLowerCaseAlpha ) />
<cfset strNumbers = "23456789" />
<cfset strOtherChars = "~!@$%^&*{}:;,.?/" />
<cfset strAllValidChars = (
strLowerCaseAlpha &
strUpperCaseAlpha &
strNumbers &
strOtherChars
) />
<cfset arrPassword = ArrayNew( 1 ) />
<cfset arrPassword=genPasswordPart(strNumbers, arguments.qtyNumbers, arrPassword)>
<cfset arrPassword=genPasswordPart(strLowerCaseAlpha, arguments.qtyLowerCaseAlpha, arrPassword)>
<cfset arrPassword=genPasswordPart(strUpperCaseAlpha, arguments.qtyUpperCaseAlpha, arrPassword)>
<cfset arrPassword=genPasswordPart(strOtherChars, arguments.qtyOtherChars, arrPassword)>
<cfset currentPWLength=arguments.min_length-ArrayLen( arrPassword )>
<!--- pad the rest of the password if needed --->
<cfif currentPWLength gt 0>
<cfset arrPassword=genPasswordPart(strLowerCaseAlpha, currentPWLength, arrPassword)>
</cfif>
<!--- shuffle --->
<cfset CreateObject( "java", "java.util.Collections" ).Shuffle(
arrPassword
) />
<!--- Array to list, makes it a string--->
<cfset strPassword = ArrayToList(
arrPassword,
""
) />
<cfreturn strPassword>
</cffunction>
<cffunction name="genPasswordPart" >
<cfargument name="stringType" required="true">
<cfargument name="quantity" required="true">
<cfargument name="passwordArray" required="true">
<cfif arguments.quantity gt 0>
<cfloop from="1" to="#arguments.quantity#" index="i">
<cfset arguments.passwordArray[ arrayLen(arguments.passwordArray)+1 ] = Mid(
arguments.stringType,
RandRange( 1, Len( arguments.stringType ) )
,1
) />
</cfloop>
</cfif>
<cfreturn arguments.passwordArray>
</cffunction>
</cfcomponent>
Thanks! This was just what I was looking for.
SWEET!! I always find the best examples on your site \m/ >< \m/
Ben,
Need to find the new way of doing the same using java/coldfusion new functions.
Thanks,
Raghuram Reddy Gottimukkula
Hyderabad India.
You're site has been my go to site for years. I put this password code into place years ago and only just now got an error from the * char when coldfusion compared it to the one in a database using SQL (where...password = <cfqueryparam value="#password#" cfsqltype="cf_sql_longvarchar" maxlength="5">). The error was: application error The cause of this output exception was that: coldfusion.tagext.sql.QueryParamTag$InvalidDataException: Invalid data value 4F5*A exceeds maxlength setting 5. I'm guessing coldfusion didn't know exactly what to do with the '*'? Now I'm wondering about the *,#, and % chars since they have can be used for other purposes. Which characters will NOT cause a problem? How about '-' and '_'. Are those safe to put in?
Once again you save me time and effort by doing the work for me.
These days I always put +bennadel into google when looking for ColdFusion answer quickly.