Created
July 7, 2016 15:56
-
-
Save ivanionut/eece96bc845a1b62f8ac894c68cc5a9b to your computer and use it in GitHub Desktop.
A ColdFusion component for testing the strength of a password, which is necessary for enhancing the security of an application.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!------------------------------------------------------------------------------ | |
|| Component : passwordCheck.cfc | |
|| Author : Jason Luttrell | |
|| Description : Functionality to test for password strength. | |
|| Public Methods : init() | |
|| : initialize component | |
|| isPasswordValid() | |
|| : returns boolean indicating whether or not the | |
|| password meets the minimum requirements. | |
|| getErrors() | |
|| : returns an HTML list of the reasons a password | |
|| does not meet the minimum requirements. | |
|| getStrength() | |
|| : returns the strength of the password (i.e., WEAK, | |
|| MEDIUM, or STRONG). | |
|| Notes : Use of this component does NOT satisfy all security | |
|| requirements. There are three more additional requirements | |
|| that must be implemented on every application: | |
|| (1) Passwords should be set to expire every 90 days and | |
|| the application should remember the last five used | |
|| passwords. | |
|| (2) The passwords must be encrypted when they are stored | |
|| in the database. | |
|| (3) When validating a user, password matching must use | |
|| CASE-SENSITIVE comparisons. By default, ColdFusion | |
|| and MS SQL Server use case-INsensitive comparisons. | |
-------------------------------------------------------------------------------> | |
<!--- | |
========================================== | |
|| SAMPLE CODE TO INVOKE THIS COMPONENT || | |
========================================== | |
<cfscript> | |
// Initialize component | |
PasswordCheckObj = createobject("component","#cfcRoot#passwordCheck"); | |
PasswordCheckObj.init( | |
form.password, | |
form.userName, | |
form.firstName, | |
form.lastName, | |
form.emailAddress, | |
isSuperAdmin | |
); | |
</cfscript> | |
<cfif not PasswordCheckObj.isPasswordValid()> | |
The password is considered #PasswordCheckObj.getStrength()# for the | |
following reasons: | |
<blockquote> | |
#PasswordCheckObj.getErrors()# | |
</blockquote> | |
</cfif> | |
===================== | |
|| END SAMPLE CODE || | |
===================== | |
---> | |
<cfcomponent output="no"> | |
<cfscript> | |
//========================================================================// | |
// INITIALIZE COMPONENT VARIABLES // | |
//========================================================================// | |
variables.instance = structNew(); | |
// Define minimum password requirements using constants | |
instance.MIN_PASSWORD_LENGTH = 8; | |
instance.MIN_DIGITS = 1; | |
instance.MIN_LOWER_CASE_LETTERS = 1; | |
instance.MIN_SPECIAL_CHARACTERS = 1; | |
instance.MIN_UPPER_CASE_LETTERS = 1; | |
instance.MIN_NON_LETTER_CHARACTERS = 2; | |
// Define what a "strong" password is for root/super admins, using constants | |
instance.OPTIMAL_PASSWORD_LENGTH = 10; | |
instance.OPTIMAL_DIGITS = 1; | |
instance.OPTIMAL_LOWER_CASE_LETTERS = 2; | |
instance.OPTIMAL_SPECIAL_CHARACTERS = 1; | |
instance.OPTIMAL_UPPER_CASE_LETTERS = 2; | |
instance.OPTIMAL_NON_LETTER_CHARACTERS = 3; | |
// Internal variables | |
instance.countDigits = 0; | |
instance.countLowerCaseLetters = 0; | |
instance.countSpecialCharacters = 0; | |
instance.countUpperCaseLetters = 0; | |
instance.emailAddress = ""; | |
instance.errorMessages = ""; | |
instance.firstName = ""; | |
instance.isPasswordValid = false; | |
instance.lastName = ""; | |
instance.password = ""; | |
instance.passwordLength = 0; | |
instance.passwordStrength = ""; | |
instance.requireOptimalPassword = false; | |
instance.userName = ""; | |
//========================================================================// | |
// *** PUBLIC METHODS *** // | |
//========================================================================// | |
//-------------------------------------------------------------------------- | |
// Function : init() | |
// Type : public | |
// Arguments : (1) password (string) - the password to test | |
// (2) userName (string) - ensures that the password is not | |
// the same as the person's user name. | |
// (3) firstName (string) - ensures that the password is not | |
// the same as the person's first name. | |
// (4) lastName (string) - ensures that the password is not | |
// the same as the person's first name. | |
// (5) emailAddress (string) - ensures that the password is | |
// not the same as the person's e-mail address. | |
// (6) requireOptimalPassword (boolean) [OPTIONAL] - test the | |
// password for the best possible entropy (for root/super | |
// admin accounts). | |
// Actions : The primary function for instantiating this component. | |
// Returns : this; the variable scope for this instance | |
//-------------------------------------------------------------------------- | |
function init(password, userName, firstName, lastName, emailAddress) | |
{ | |
// Define local scope | |
var local = structNew(); | |
// Get function arguments | |
local.password = trim(arguments.password); | |
local.userName = trim(arguments.userName); | |
local.firstName = trim(arguments.firstName); | |
local.lastName = trim(arguments.lastName); | |
local.emailAddress = trim(arguments.emailAddress); | |
if (arrayLen(arguments) gte 6) | |
local.requireOptimalPassword = arguments[6]; | |
else | |
local.requireOptimalPassword = false; | |
// Get password length | |
local.passwordLength = len(local.password); | |
// Count number of lower-case letters | |
local.countLowerCaseLetters = 0; | |
for (i=1; i lte local.passwordLength; i=i+1) { | |
local.letterIndex = reFind("[a-z]", local.password, i); | |
if (local.letterIndex gt 0) { | |
i = local.letterIndex; | |
local.countLowerCaseLetters = local.countLowerCaseLetters + 1; | |
} | |
} | |
// Count number of upper-case letters | |
local.countUpperCaseLetters = 0; | |
for (i=1; i lte local.passwordLength; i=i+1) { | |
local.letterIndex = reFind("[A-Z]", local.password, i); | |
if (local.letterIndex gt 0) { | |
i = local.letterIndex; | |
local.countUpperCaseLetters = local.countUpperCaseLetters + 1; | |
} | |
} | |
// Count number of digits | |
local.countDigits = 0; | |
for (i=1; i lte local.passwordLength; i=i+1) { | |
local.numberIndex = reFind("[0-9]", local.password, i); | |
if (local.numberIndex gt 0) { | |
i = local.numberIndex; | |
local.countDigits = local.countDigits + 1; | |
} | |
} | |
// Count number of special chars | |
local.countSpecialCharacters = 0; | |
for (i=1; i lte local.passwordLength; i=i+1) { | |
local.specialCharIndex = reFind("[\W]", local.password, i); | |
if (local.specialCharIndex gt 0) { | |
i = local.specialCharIndex; | |
local.countSpecialCharacters = local.countSpecialCharacters + 1; | |
} | |
} | |
// Save variables to component scope | |
instance.countDigits = local.countDigits; | |
instance.countLowerCaseLetters = local.countLowerCaseLetters; | |
instance.countSpecialCharacters = local.countSpecialCharacters; | |
instance.countUpperCaseLetters = local.countUpperCaseLetters; | |
instance.emailAddress = local.emailAddress; | |
instance.firstName = local.firstName; | |
instance.lastName = local.lastName; | |
instance.password = local.password; | |
instance.passwordLength = local.passwordLength; | |
instance.requireOptimalPassword = local.requireOptimalPassword; | |
instance.userName = local.userName; | |
// Perform password testing | |
testPassword(); | |
// Return the "this" variable scope | |
return this; | |
} | |
//-------------------------------------------------------------------------- | |
// Function : isPasswordValid() | |
// Type : public | |
// Arguments : None | |
// Actions : Tests password. | |
// Returns : Boolean; indicates whether or not the password meets the | |
// minimum requirements. | |
//-------------------------------------------------------------------------- | |
function isPasswordValid() | |
{ | |
// Return Boolean variable indicating whether or not the password meets | |
// the minimum requirements | |
return instance.isPasswordValid; | |
} | |
//-------------------------------------------------------------------------- | |
// Function : getErrors() | |
// Type : public | |
// Arguments : None | |
// Actions : Tests password. | |
// Returns : String; an HTML list of the reasons a password does not | |
// meet the minimum requirements. | |
//-------------------------------------------------------------------------- | |
function getErrors() | |
{ | |
// Return an HTML list of the reasons a password does not meet the | |
// minimum requirements | |
return instance.errorMessages; | |
} | |
//-------------------------------------------------------------------------- | |
// Function : getStrength() | |
// Type : public | |
// Arguments : None | |
// Actions : Tests password. | |
// Returns : String; the strength of the password (i.e., WEAK, MEDIUM, | |
// or STRONG). | |
//-------------------------------------------------------------------------- | |
function getStrength() | |
{ | |
// Return the strength of the password (i.e., WEAK, MEDIUM, or STRONG) | |
return instance.passwordStrength; | |
} | |
//========================================================================// | |
// *** PRIVATE METHODS *** // | |
//========================================================================// | |
//-------------------------------------------------------------------------- | |
// Function : testPassword() | |
// Type : private | |
// Arguments : None | |
// Actions : Tests password. | |
// Returns : None | |
//-------------------------------------------------------------------------- | |
function testPassword() | |
{ | |
// If the password meets the optimal, strongest requirements... | |
if (instance.passwordLength gte | |
instance.OPTIMAL_PASSWORD_LENGTH | |
and | |
instance.countLowerCaseLetters gte | |
instance.OPTIMAL_LOWER_CASE_LETTERS | |
and | |
instance.countUpperCaseLetters gte | |
instance.OPTIMAL_UPPER_CASE_LETTERS | |
and | |
instance.countDigits gte | |
instance.OPTIMAL_DIGITS | |
and | |
instance.countSpecialCharacters gte | |
instance.OPTIMAL_SPECIAL_CHARACTERS | |
) { | |
// Set the password strength indicating that it meets the optimal, | |
// strongest requirements | |
instance.passwordStrength = "STRONG"; | |
} | |
// Otherwise, if the password meets the minimum requirements... | |
else if (instance.passwordLength gte | |
instance.MIN_PASSWORD_LENGTH | |
and | |
instance.countLowerCaseLetters gte | |
instance.MIN_LOWER_CASE_LETTERS | |
and | |
instance.countUpperCaseLetters gte | |
instance.MIN_UPPER_CASE_LETTERS | |
and | |
instance.countDigits gte | |
instance.MIN_DIGITS | |
and | |
instance.countSpecialCharacters gte | |
instance.MIN_SPECIAL_CHARACTERS | |
) { | |
// Set the password strength indicating that it meets the minimum | |
// requirements | |
instance.passwordStrength = "MEDIUM"; | |
} | |
// Otherwise, if the password does not even meet the minimum | |
// requirements... | |
else { | |
// Set the password strength indicating that it is a weak password. | |
instance.passwordStrength = "WEAK"; | |
} | |
// If password meets requirements... | |
if ( | |
( | |
instance.requireOptimalPassword eq false | |
and | |
( | |
instance.passwordStrength eq "MEDIUM" | |
or | |
instance.passwordStrength eq "STRONG" | |
) | |
) | |
or | |
( | |
instance.requireOptimalPassword eq true | |
and | |
instance.passwordStrength eq "STRONG" | |
) | |
) { | |
// Indicate that the password is valid | |
instance.isPasswordValid = true; | |
} | |
// Otherwise, if the password does not meet requirements... | |
else { | |
// Indicate that the password is not valid | |
instance.isPasswordValid = false; | |
// If password does not meet the minimum length... | |
if ( | |
instance.requireOptimalPassword eq false | |
and | |
instance.passwordLength lt | |
instance.MIN_PASSWORD_LENGTH | |
) { | |
// Specify the reason | |
instance.errorMessages = instance.errorMessages & ' | |
<li> | |
Please check that the password is at least | |
#instance.MIN_PASSWORD_LENGTH# | |
#iif(instance.MIN_PASSWORD_LENGTH eq 1, | |
DE("character"), | |
DE("characters"))# | |
in length. | |
</li> | |
'; | |
} | |
else if ( | |
instance.requireOptimalPassword eq true | |
and | |
instance.passwordLength lt | |
instance.OPTIMAL_PASSWORD_LENGTH | |
) { | |
// Specify the reason | |
instance.errorMessages = instance.errorMessages & ' | |
<li> | |
A root/super administrator password must | |
be at least #instance.OPTIMAL_PASSWORD_LENGTH# | |
#iif(instance.OPTIMAL_PASSWORD_LENGTH eq 1, | |
DE("character"), | |
DE("characters"))# | |
in length. | |
</li> | |
'; | |
} | |
// If password does not have the minimum number of lower-case | |
// letters... | |
if ( | |
instance.requireOptimalPassword eq false | |
and | |
instance.countLowerCaseLetters lt | |
instance.MIN_LOWER_CASE_LETTERS | |
) { | |
// Specify the reason | |
instance.errorMessages = instance.errorMessages & ' | |
<li> | |
Please check that the password has at least | |
#instance.MIN_LOWER_CASE_LETTERS# lower-case | |
#iif(instance.MIN_LOWER_CASE_LETTERS eq 1, | |
DE("letter."), | |
DE("letters."))# | |
</li> | |
'; | |
} | |
else if ( | |
instance.requireOptimalPassword eq true | |
and | |
instance.countLowerCaseLetters lt | |
instance.OPTIMAL_LOWER_CASE_LETTERS | |
) { | |
// Specify the reason | |
instance.errorMessages = instance.errorMessages & ' | |
<li> | |
A root/super administrator password must have at least | |
#instance.OPTIMAL_LOWER_CASE_LETTERS# lower-case | |
#iif(instance.OPTIMAL_LOWER_CASE_LETTERS eq 1, | |
DE("letter."), | |
DE("letters."))# | |
</li> | |
'; | |
} | |
// If password does not have the minimum number of upper-case | |
// letters... | |
if ( | |
instance.requireOptimalPassword eq false | |
and | |
instance.countUpperCaseLetters lt | |
instance.MIN_UPPER_CASE_LETTERS | |
) { | |
// Specify the reason | |
instance.errorMessages = instance.errorMessages & ' | |
<li> | |
Please check that the password has at least | |
#instance.MIN_UPPER_CASE_LETTERS# upper-case | |
#iif(instance.MIN_UPPER_CASE_LETTERS eq 1, | |
DE("letter."), | |
DE("letters."))# | |
</li> | |
'; | |
} | |
else if ( | |
instance.requireOptimalPassword eq true | |
and | |
instance.countUpperCaseLetters lt | |
instance.OPTIMAL_UPPER_CASE_LETTERS | |
) { | |
// Specify the reason | |
instance.errorMessages = instance.errorMessages & ' | |
<li> | |
A root/super administrator password must have at least | |
#instance.OPTIMAL_UPPER_CASE_LETTERS# upper-case | |
#iif(instance.OPTIMAL_UPPER_CASE_LETTERS eq 1, | |
DE("letter."), | |
DE("letters."))# | |
</li> | |
'; | |
} | |
// If password does not have the minimum required numbers... | |
if ( | |
instance.requireOptimalPassword eq false | |
and | |
instance.countDigits lt instance.MIN_DIGITS | |
) { | |
// Specify the reason | |
instance.errorMessages = instance.errorMessages & ' | |
<li> | |
The password must have at least #instance.MIN_DIGITS# | |
#iif(instance.MIN_DIGITS eq 1, | |
DE("digit."), | |
DE("digits."))# | |
</li> | |
'; | |
} | |
else if ( | |
instance.requireOptimalPassword eq true | |
and | |
instance.countDigits lt instance.OPTIMAL_DIGITS | |
) { | |
// Specify the reason | |
instance.errorMessages = instance.errorMessages & ' | |
<li> | |
A root/super administrator password must have at least | |
#instance.OPTIMAL_DIGITS# | |
#iif(instance.OPTIMAL_DIGITS eq 1, | |
DE("digit."), | |
DE("digits."))# | |
</li> | |
'; | |
} | |
// If password does not have the minimum required special | |
// characters... | |
if ( | |
instance.requireOptimalPassword eq false | |
and | |
instance.countSpecialCharacters lt | |
instance.MIN_SPECIAL_CHARACTERS | |
) { | |
// Specify the reason | |
instance.errorMessages = instance.errorMessages & ' | |
<li> | |
Please make sure that the password has at least | |
#instance.MIN_SPECIAL_CHARACTERS# special | |
#iif(instance.MIN_SPECIAL_CHARACTERS eq 1, | |
DE("character."), | |
DE("characters."))# | |
Special characters may include any of the following: | |
<blockquote> | |
~ ! @ ## $ % ^ & * ( ) _ + ` { } | : < > | |
? [ ] \ ; " , . / | |
</blockquote> | |
</li> | |
'; | |
} | |
else if ( | |
instance.requireOptimalPassword eq true | |
and | |
instance.countSpecialCharacters lt | |
instance.OPTIMAL_SPECIAL_CHARACTERS | |
) { | |
// Specify the reason | |
instance.errorMessages = instance.errorMessages & ' | |
<li> | |
A root/super administrator password must have at least | |
#instance.OPTIMAL_SPECIAL_CHARACTERS# special | |
#iif(instance.OPTIMAL_SPECIAL_CHARACTERS eq 1, | |
DE("character."), | |
DE("characters."))# | |
Special characters may include any of the following: | |
<blockquote> | |
~ ! @ ## $ % ^ & * ( ) _ + ` { } | : < > ? | |
[ ] \ ; " , . / | |
</blockquote> | |
</li> | |
'; | |
} | |
// If password does not have the minimum required special | |
// characters... | |
if ( | |
instance.requireOptimalPassword eq false | |
and | |
( | |
(instance.MIN_SPECIAL_CHARACTERS + instance.MIN_DIGITS) lt | |
instance.MIN_NON_LETTER_CHARACTERS | |
) | |
and | |
( | |
(instance.countSpecialCharacters + instance.countDigits) lt | |
instance.MIN_NON_LETTER_CHARACTERS | |
) | |
) { | |
// Specify the reason | |
instance.errorMessages = instance.errorMessages & ' | |
<li> | |
Please make sure that the password has at least | |
#instance.MIN_NON_LETTER_CHARACTERS# non-letter | |
#iif(instance.MIN_NON_LETTER_CHARACTERS eq 1, | |
DE("character."), | |
DE("characters."))# | |
</li> | |
'; | |
} | |
else if ( | |
instance.requireOptimalPassword eq true | |
and | |
( | |
( | |
instance.OPTIMAL_SPECIAL_CHARACTERS | |
+ instance.OPTIMAL_DIGITS | |
) lt instance.OPTIMAL_NON_LETTER_CHARACTERS | |
) | |
and | |
( | |
( | |
instance.countSpecialCharacters | |
+ instance.countDigits | |
) lt instance.OPTIMAL_NON_LETTER_CHARACTERS | |
) | |
) { | |
// Specify the reason | |
instance.errorMessages = instance.errorMessages & ' | |
<li> | |
A root/super administrator password must have at least | |
#instance.OPTIMAL_NON_LETTER_CHARACTERS# non-letter | |
#iif(instance.OPTIMAL_NON_LETTER_CHARACTERS eq 1, | |
DE("character."), | |
DE("characters."))# | |
</li> | |
'; | |
} | |
} | |
// =============================== | |
// Test for criteria in which to | |
// reject passwords outright. | |
// =============================== | |
// If there is a space... | |
if (find(" ", instance.password)) { | |
// Reject password | |
instance.isPasswordValid = false; | |
instance.passwordStrength = "WEAK"; | |
// Specify the reason | |
instance.errorMessages = instance.errorMessages & ' | |
<li> | |
The password cannot contain a space. | |
</li> | |
'; | |
} | |
// If there is a single-quote... | |
if (find("'", instance.password)) { | |
// Reject password | |
instance.isPasswordValid = false; | |
instance.passwordStrength = "WEAK"; | |
// Specify the reason | |
instance.errorMessages = instance.errorMessages & " | |
<li> | |
The password cannot contain a single-quote character | |
[ <em>'</em> ]. | |
</li> | |
"; | |
} | |
// Get first and last instance of non-letter characters | |
firstNonLetterCharIndex = reFind("[0-9\W]", instance.password); | |
lastNonLetterCharIndex = reFind("[0-9\W]", reverse(instance.password)); | |
charIndex = reFind("[A-Za-z]",instance.password); | |
// This check only applies for passwords that already contain a number | |
// or special character... | |
if (charIndex gt 0 | |
and | |
firstNonLetterCharIndex gt 0) { | |
// Extract all characters after first instance of non-letter | |
if (firstNonLetterCharIndex lt instance.passwordLength | |
and | |
firstNonLetterCharIndex gt 0 | |
) { | |
sectionAfterFirstNonLetterChar | |
= right(instance.password, | |
(instance.passwordLength +1) | |
- firstNonLetterCharIndex); | |
// Look for letters after first instance of non-letter | |
local.letterIndex = reFind("[A-Za-z]", | |
sectionAfterFirstNonLetterChar); | |
// If there are none... | |
if (local.letterIndex eq 0) { | |
// Reject password | |
instance.isPasswordValid = false; | |
instance.passwordStrength = "WEAK"; | |
// Specify the reason | |
instance.errorMessages = instance.errorMessages & ' | |
<li> | |
The password cannot have all numbers and/or special | |
characters at the end. | |
</li> | |
'; | |
} | |
} | |
} | |
if (charIndex gt 0 | |
and | |
lastNonLetterCharIndex gt 0) { | |
// Extract all characters before last instance of non-letter | |
if (lastNonLetterCharIndex lt instance.passwordLength | |
and | |
lastNonLetterCharIndex gt 0 | |
) { | |
sectionBeforeLastNonLetterChar | |
= right(reverse(instance.password), | |
(instance.passwordLength +1) | |
-lastNonLetterCharIndex); | |
// Look for letters before last instance of non-letter | |
local.letterIndex = reFind("[A-Za-z]", | |
sectionBeforeLastNonLetterChar); | |
// If there are none... | |
if (local.letterIndex eq 0) { | |
// Reject password | |
instance.isPasswordValid = false; | |
instance.passwordStrength = "WEAK"; | |
// Specify the reason | |
instance.errorMessages = instance.errorMessages & ' | |
<li> | |
The password cannot have all numbers and/or special | |
characters at the beginning. | |
</li> | |
'; | |
} | |
} | |
} | |
// Extract the name from the email address | |
if (find("@", instance.emailAddress) gt 1) { | |
emailAddressName = mid(instance.emailAddress, | |
1, | |
find("@", instance.emailAddress)-1 | |
); | |
} | |
else { | |
emailAddressName = instance.emailAddress; | |
} | |
// If the password is the same as the user name or email address... | |
if ( | |
( | |
len(trim(emailAddressName)) gt 0 | |
and | |
findNoCase(emailAddressName, instance.password) | |
) | |
or | |
( | |
len(trim(instance.userName)) gt 0 | |
and | |
findNoCase(instance.userName, instance.password) | |
) | |
or | |
( | |
len(trim(instance.firstName)) gt 0 | |
and | |
findNoCase(instance.firstName, instance.password) | |
) | |
or | |
( | |
len(trim(instance.lastName)) gt 0 | |
and | |
findNoCase(instance.lastName, instance.password) | |
) | |
) { | |
// Reject password | |
instance.isPasswordValid = false; | |
instance.passwordStrength = "WEAK"; | |
// Specify the reason | |
instance.errorMessages = instance.errorMessages & ' | |
<li> | |
The password cannot contain any personally | |
identifying information already associated with | |
your account. | |
</li> | |
'; | |
} | |
} | |
</cfscript> | |
</cfcomponent> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment