Password Validation Class Using RegEx in C-Sharp

I have a client that recently asked me to add password validation on a web application.  Their existing application had no password validation and users could enter any password into the system with no regards to how secure it was.  I was asked to implement the following password constraints:

  1. Passwords must be at least 8 characters in length
  2. Passwords must not have the login username in the password
  3. Passwords cannot have 3 or more consecutive incremental characters, such as “abc” or “123.”
  4. Passwords must pass at least 3 of the following 4 tests:
    1. Password must have at least 1 lowercase character
    2. Password must have at least 1 uppercase character
    3. Password must have at least 1 numeric character
    4. Password must have at least 1 special character

All of this has been done a million times, and if you Google it, there are tons of examples, but no single solution encompassed every requirement.  So I threw together a quick class to solve the problem.  What I am about to share is not the final class I developed – it does not have all the configuration options I added to it later, but it is the first fully functional class that addressed the need.

Most of the tests are easily performed using simple regular expressions, with the exception of numebr 3, “Passwords cannot have 3 or more consecutive characters, such as “abc” or “123.””  For that one, I wrote a method that converted the string into an integer array of ASCII values for each digit and then I looped through the array looking for consecutive numeric ASCII values.  This class should work for anyone – and you can extend this basic class and make it more dynamic like I did for my client easily.  Have fun and good luck!

PasswordOps.cs

//-----------------------------------------------------------------------
// Class Name:  PasswordOps.cs
// Author:  Ron Sparks - The Binary Biker
//
// This class is designed to check passwords for <company> standards
//
// <copyright file="PasswordOps.cs" company="BinaryBiker.com">
//      Copyright Ron Sparks.  All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

using System;
using System.Text;
using System.Text.RegularExpressions;

namespace PasswordRemediation
{
    /// <summary>
    /// Class designed to ensure that passwords meet ~client~ standards
    /// </summary>
    class PasswordOps
    {
        #region Constants

        // RegEx pattern that checks a string for lower case characters
        private const string LowerCasePattern = "^.*(?=.*[a-z]).*$";

        // RegEx pattern that checks a string for upper case characters
        private const string UpperCasePattern = "^.*(?=.*[A-Z]).*$";

        // RegEx pattern that checks a string for a numeric digit
        private const string NumericPattern = "^.*(?=.*[\\d]).*$";

        // RegEx pattern that checks a string for special characters
        private const string SpecialCharacterPattern = "^.*(?=.*[\\W]).*$";

        // RegEx Pattern that checks a string for minimum length of 8 charcters
        private const string MinimumLengthPattern = "^.*(?=.{8,}).*$";

        // The number of RegEx tests that must pass for validation to succeed
        private const int NumberOfTestsReqiredToPass = 3;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or set the error message after the call to ValidatPassword
        /// </summary>
        public string ErrorMessage { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// Checks a password for required pattern parameters
        /// </summary>
        /// <param name="username">username of the login user</param>
        /// <param name="password">password supplied</param>
        /// <returns>Boolean success flag</returns>
        public bool ValidatePassword(string username, string password)
        {
            var numTestsPassed = 0;

            // password cannot conatin username
            if (password.ToLower().IndexOf(username.ToLower()) > -1)
            {
                ErrorMessage = "Passwords cannot contain usernames.";
                return false;
            }

            // password cannot contain more than 2 consecutive characters
            if (HasConsecutiveCharacters(password, 2))
            {
                ErrorMessage = "Password cannot have 3 or more consequtive characters in it ('abc', '123')";
                return false;
            }

            // password must be at least 8 characters long
            if (Regex.IsMatch(password, MinimumLengthPattern, RegexOptions.IgnoreCase))
            {
                // see if password has a lowercase character
                var isMatch = Regex.IsMatch(password, LowerCasePattern);
                var messageBuilder = ErrorMessage;
                ErrorMessage = isMatch ? messageBuilder  + string.Empty : messageBuilder  + "Passwords must have at least one lowercase character." +  Environment.NewLine;
                numTestsPassed  = isMatch ? 1 : 0;

                // see if password has an uppercase character
                isMatch = Regex.IsMatch(password, UpperCasePattern);
                messageBuilder = ErrorMessage;
                ErrorMessage = isMatch ? messageBuilder +  string.Empty : messageBuilder +  "Passwords must have at least one uppercase character."  + Environment.NewLine;
                numTestsPassed  = isMatch ? 1 : 0;

                // see if password has a numeric character
                isMatch = Regex.IsMatch(password, NumericPattern);
                messageBuilder = ErrorMessage;
                ErrorMessage = isMatch ? messageBuilder +  string.Empty : messageBuilder  + "Passwords must have at least one numeric character."  + Environment.NewLine;
                numTestsPassed  = isMatch ? 1 : 0;

                // see if password has any special characters
                isMatch = Regex.IsMatch(password, SpecialCharacterPattern);
                messageBuilder = ErrorMessage;
                ErrorMessage = isMatch ? messageBuilder +  string.Empty : messageBuilder  + "Passwords must have at least one special character."  + Environment.NewLine;
                numTestsPassed  = isMatch ? 1 : 0;

                messageBuilder = ErrorMessage;
                ErrorMessage = numTestsPassed < 3
                                   ? "Password must meet 3 of the following 4 criteria: 1 uppercase character, 1 lowercase character, 1 numeric digit, 1 special character.  Your password has the following potential errors: " + 
                                     Environment.NewLine +   messageBuilder
                                   : string.Empty;

                // If enough tests have passed, the validation is a success.
                return numTestsPassed > NumberOfTestsReqiredToPass - 1 ? true : false;

            }

            ErrorMessage = "Passwords must be at least 8 characters long";
            return false;
        }
        #endregion

        #region Private Static Methods

        /// <summary>
        /// Checks a string for consecutive ascii characters
        /// </summary>
        /// <param name="input">the string to check</param>
        /// <param name="maxChars">the threshold of consecutive characters to check for</param>
        /// <returns>Boolean.  True if threshold hit, false otherwise</returns>
        private static bool HasConsecutiveCharacters(string input, int maxChars)
        {
            var inputAscArray = new int[input.Length];
            var numConsecutiveCharacters = 1;
            var lastAscValue = 0;
            var ascii = Encoding.ASCII;

            for (var x = 0; x < input.Length; x++  )
            {
                var encodedBytes = ascii.GetBytes(input.Substring(x, 1));
                inputAscArray[x] = encodedBytes[0];
            }

            for (var x = 0; x < input.Length; x++ )
            {
                if (inputAscArray[x] == lastAscValue   1)
                {
                    numConsecutiveCharacters ++ ;
                    if (numConsecutiveCharacters > maxChars)
                    { break;}
                }
                else
                {
                    numConsecutiveCharacters = 1;
                }

                lastAscValue = inputAscArray[x];
            }

            return numConsecutiveCharacters > maxChars;

        }

        #endregion
    }
}
Advertisements

No comments yet... Be the first to leave a reply!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: