A Simple Password Strength Function - Security Series #4.1

So someone asked me about creating a regular expression to validate a user's password to see if it conformed to the many rules that we like to place on them. I looked into this a bit and thought about it while I was driving to work, and it occurs to me that we would have one UGLY regular expression on our hands if we needed it to enforce everything we discussed in my last post.

Anther problem with using a gianormous regex expression is that it is not possible to report back to the user what they got wrong. Either the expression passes, or it doesn't.

So, it seems to me that a simple User Defined Function(UDF) would solve this problem handily.


<!--- I recommend placing this inside of a CFC and using it as a supporting function to your user registration or password change function, hence the access="private" --->
<cffunction name="checkPassword" access="private" returntype="array" hint="I check password strength and determine if it is up to snuff, I return an array of error messages">
    <!--- Accept username arg for comparing later in the function --->
    <cfargument name="usernameIn" required="true" type="string" hint="Send in username as string">
    <!--- Accept password argument, default to blank string should be ok cause it will fail all of the tests --->
    <cfargument name="passwordIn" required="false" default="" type="string" hint="Send in password as a string, default is a blank string, which will fail">

    <!--- Initialize return variable --->
    <cfset var aErrors = ArrayNew(1) />
    
    <!--- If the password is more than X and less than Y, add an error. You could make this two functions (one for the lower limit and one for the upper), but why bother, can your users count? --->
    <cfif Len(arguments.passwordIn) LT 8 OR Len(arguments.passwordIn) GT 25>
        <cfset ArrayAppend(aErrors, "Your password must be between 8 and 25 characters long") />
    </cfif>
    
    <!--- Check for atleast 1 uppercase letter --->        
    <cfif NOT REFind('[A-Z]+', arguments.passwordIn)>
        <cfset ArrayAppend(aErrors, "Your password must contain at least 1 uppercase letter") />
    </cfif>
    
    <!--- Check for atleast 1 lowercase letter --->
    <cfif NOT REFind('[a-z]+', arguments.passwordIn)>
        <cfset ArrayAppend(aErrors, "Your password must contain at least 1 lowercase letter") />
    </cfif>
    
    <!--- Check for atleast 1 numeral --->
    <cfif NOT REFind('[0-9]+', arguments.passwordIn)>
        <cfset ArrayAppend(aErrors, "Your password must contain at least 1 numeral") />
    </cfif>

    <!--- Check for one of the predfined special characters, you can add more by seperating each character with a pipe(|) --->
    <cfif NOT REFind("[^\w\d\s]+", arguments.passwordIn)>
        <cfset ArrayAppend(aErrors, "Your password must contain at least 1 special character") />
    </cfif>

    <!--- Check to see if the password contains the username --->
    <cfif findNoCase(arguments.usernameIn, arguments.passwordIn)>
        <cfset ArrayAppend(aErrors, "Your password cannot contain your username") />
    </cfif>
    
    <!--- Make sure password contains no spaces --->
    <cfif arguments.passwordIn CONTAINS " ">
        <cfset ArrayAppend(aErrors, "Your password cannot contain spaces") />
    </cfif>
    
    <!--- Make sure password is not a date --->
    <cfif IsDate(arguments.passwordIn)>
        <cfset ArrayAppend(aErrors, "Your password cannot be a date") />
    </cfif>

    <!--- return the array of errors. On the other end you can do a check of <cfif ArrayLen(aErrors) EQ true>There are errors</cfif> --->
    <cfreturn aErrors />
</cffunction>

Obviously this is more verbose than a super regex, but in the long run, I think it is more functional(no pun intended :).

Comments
robs's Gravatar

hi



the correct regex would be


"[;:@!$##%^&*()_\-+='\\\|{}?/,.]"


for special characters.



you can also omit the + in each regex



cheers

# Posted By robs | 5/13/08 12:15 PM
Jason Dean's Gravatar

@robs - Thanks. I tried that out and it seems to work great. I am still somewhat of a n00b when it comes to regex.

# Posted By Jason Dean | 5/13/08 12:20 PM
Dave's Gravatar So i konw this is gonna sound dumb but I'm just starting to get into UDFs, how do I call the function to test the username and password?
# Posted By Dave | 7/22/08 7:41 PM
Jason Dean's Gravatar @dave

Well first we need to know the location of the file that holds the function. Lets say it is located at /cfc/loginService.cfc

From the page that is handling the form submission you would call this function like this:

<cfset loginService = createObject('component', 'cfc.loginService')>
<cfset aPasswordErrors = loginService.checkPassword(form.password, form.username) />

The first line there instantiates the loginService. This only needs to be done once in the calling page, regardless of how many functions you are using. So let's say that you also had a function inside of loginService called checkUsername() that checked the user name for certain properties and returned another array of errors, then we would have:

<cfset loginService = createObject('component', 'cfc.loginService')>
<cfset aUsernameErrors = loginService.checkUsername(form.username) />
<cfset aPasswordErrors = loginService.checkPassword(form.password, form.username) />

Once the loginService has been instantiated in a page request, it does not need to be again. Now, any errors that were returned would be contained in the arrays: aUsernameErrors and aPasswordErrors.

I hope this makes sense.
# Posted By Jason Dean | 7/22/08 8:27 PM
Dave's Gravatar Thanks for getting back to me. I'm still a little confused. I understand about creating the cfc in a seperate file and I understand that we're setting the arrays to variables. But where is the right place to call the array to check the password? I dont want to do it on submit right, so is best practice to use an onChasnge or something?

My other question is will this work with a Flash form? I created a little tab navigator to update personal infomration and I'd like to itegrate a password checker in this form. I originaly intended to make two password fields and let the function compare them to eachother. I like some of the if's you used to secure the password further so I'd like to use them as well.

Dont wanna post the code without your ok, cause its kinda long, but if you like I can show you. thanks
# Posted By Dave | 7/23/08 5:30 AM
choop's Gravatar is zero no longer a valid numeral?

<cfif NOT REFind('[1-9]+', arguments.passwordIn)>

should be

<cfif NOT REFind('[1-9]+', arguments.passwordIn)>
# Posted By choop | 4/19/11 12:01 PM
Jason Dean's Gravatar I think you meant to say

<cfif NOT REFind('[0-9]+', arguments.passwordIn)>

just like I did.
# Posted By Jason Dean | 4/19/11 2:04 PM
Doug's Gravatar Awhile back I did something similiar - one requirement was that at least a capital, lowercase, number and special character be used in the password. A single error returned if these rules were not met.

<cfset check = iif((refind("[A-Z]", trim(password), 1)) gt 0,1,0) +
iif((refind("[a-z]", trim(password), 1)) gt 0,1,0) +
iif((refind("[0-9]", trim(password), 1)) gt 0,1,0) +
iif((refind("[[:punct:]]", trim(password), 1)) gt 0,1,0)>

<cfif check lt 4>
..error..
</cfif>
# Posted By Doug | 11/1/11 10:43 AM
Jason Dean's Gravatar @Doug, Right, I could have gone that route. But having a single error, in my opinion, is not a good user experience. You cannot tell the user what they did wrong.
# Posted By Jason Dean | 11/1/11 11:41 AM
Dawn's Gravatar Have you had any problems with the ~ special character being used in a password? It seems if it is used like ~Password12~ it won't allow the person to log in. Any thoughts on what the problem is?
# Posted By Dawn | 9/10/14 11:16 AM
Jason Dean's Gravatar No special characters should give you problems with passwords if you are properly hashing.
# Posted By Jason Dean | 9/11/14 8:58 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1. Contact Blog Owner