Checking the Strength of a Password

In this article I want to talk about a recent password strength checker that I build for my open source application SafePad.

Password Strength Indicator in SafePad

Password Strength Indicator in SafePad

 First of all we have a public enumeration that contains the password score results.

namespace HauntedHouseSoftware.SecureNotePad.DomainObjects
{
    public enum PasswordScore
    {
        Blank = 0,
        VeryWeak = 1,
        Weak = 2,
        Medium = 3,
        Strong = 4,
        VeryStrong = 5
    }
}

To determine the actual score we base it on the following points:

    • Is the password field empty?
    • Is it less than 4 characters long?
    • Is it less than 8 characters long?
    • Is it less than 10 characters long?
    • Does the password contain any numbers?
    • Does the password contain both upper and lower case characters?
    • Does the password contain non alpha and numeric characters like !”£$%^&*()?

This is illustrated by the following code:

      public static PasswordScore CheckStrength(string password)
        {
            int score = 0;

            if (string.IsNullOrEmpty(password))
            {
                return PasswordScore.Blank;
            }

            if (password.Length < 1)
            {
                return PasswordScore.Blank;
            }

            if (password.Length < 4)
            {
                return PasswordScore.VeryWeak;
            }

            if (password.Length >= 8)
            {
                score++;
            }

            if (password.Length >= 10)
            {
                score++;
            }

            if (Regex.Match(password, @"\d", RegexOptions.ECMAScript).Success)
            {
                score++;
            }

            if (Regex.Match(password, @"[a-z]", RegexOptions.ECMAScript).Success &&
                Regex.Match(password, @"[A-Z]", RegexOptions.ECMAScript).Success)
            {
                score++;
            }

            if (Regex.Match(password, @"[!,@,#,$,%,^,&,*,?,_,~,-,£,(,)]", RegexOptions.ECMAScript).Success)
            {
                score++;
            }

            return (PasswordScore)score;
        }

On top of this I have extended the counter to look for common passwords. There was an interesting list published in 2012 of 25 of the most common passwords. The data was compiled from password lists that had been stolen and published on the internet by hacker groups. I implemented this by doing a dictionary check, but what I also do is perform common character substitutions and test those too, for example :

    • password
    • p455w0rd
    • [email protected]

  It’s obviously not an exhaustive, but it can be added too easily.

private readonly static string[] _weakPasswordList = { "password", "123456", "1234567", "12345678", "abc123", "qwerty", "monkey", "letmein", "dragon", "111111", "baseball", "iloveyou", "trustno1", "sunshine", "master", "123123", "welcome", "shadow", "ashley", "football", "jesus", "michael", "ninja", "mustang", "password1" };

       private static bool IsPasswordInWeakList(string password)
        {
            foreach (string weakPassword in _weakPasswordList)
            {
                if (string.Compare(password,weakPassword,System.StringComparison.OrdinalIgnoreCase) == 0)
                {
                    return true;
                }

                if (PerformSubstitutions(weakPassword, password))
                {
                    return true;
                }
            }

            return false;
        }

        private static bool PerformSubstitutions(string weakPassword, string password)
        {
            char[] vowels =            { 'A', 'a', 'e', 'i', 'o', 's', 'S'};
            char[] vowelSubstitution = { '4', '@', '3', '1', '0', '$', '5' };

            ReplaceLettersWithSubStitutions(password,vowels, vowelSubstitution);

            if (string.Compare(ReplaceLettersWithSubStitutions(weakPassword, vowels, vowelSubstitution), password, System.StringComparison.OrdinalIgnoreCase) == 0)
            {
                return true;
            }

            char[] qwerty = { 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P' };
            char[] qwertySubstitution = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};

            if (string.Compare(ReplaceLettersWithSubStitutions(weakPassword, qwerty, qwertySubstitution), password, System.StringComparison.OrdinalIgnoreCase) == 0)
            {
                return true;
            }

            return false;
        }

        private static string ReplaceLettersWithSubStitutions(string password, char[] original, char[] substitution)
        {
            string newPassword = string.Empty;

            foreach (char c in password)
            {
                bool numberAdded = false;

                for (int q = 0; q < original.Length; q++)
                {
                    if (c == original[q])
                    {
                        newPassword = newPassword + substitution[q];
                        numberAdded = true;
                        break;
                    }
                }

                if (!numberAdded)
                {
                    newPassword = newPassword + c;
                }
            }

            return newPassword;
        }
    }

For completeness, here is the full listing below. There are probably 100’s of ways of checking password strength. If you have an alternative way of doing it then please leave a comment.

You use the password strength checker as follows:

PasswordScore score = PasswordStrength.CheckStrength("45784ngryDw4rf99!");

 

using System.Text.RegularExpressions;
namespace HauntedHouseSoftware.SecureNotePad.DomainObjects
{
    public sealed class PasswordStrength
    {
        private PasswordStrength()
        { }

        private readonly static string[] _weakPasswordList = { "password", "123456", "1234567", "12345678", "abc123", "qwerty", "monkey", "letmein", "dragon", "111111", "baseball", "iloveyou", "trustno1", "sunshine", "master", "123123", "welcome", "shadow", "ashley", "football", "jesus", "michael", "ninja", "mustang", "password1" };

        public static PasswordScore CheckStrength(string password)
        {
            int score = 0;

            if (string.IsNullOrEmpty(password))
            {
                return PasswordScore.Blank;
            }

            if (IsPasswordInWeakList(password))
            {
                return PasswordScore.Weak;
            }

            if (password.Length < 1)
            {
                return PasswordScore.Blank;
            }

            if (password.Length < 4)
            {
                return PasswordScore.VeryWeak;
            }             

            if (password.Length >= 8)
            {
                score++;
            }

            if (password.Length >= 10)
            {
                score++;
            }

            if (Regex.Match(password, @"\d", RegexOptions.ECMAScript).Success)
            {
                score++;
            }

            if (Regex.Match(password, @"[a-z]", RegexOptions.ECMAScript).Success &&
                Regex.Match(password, @"[A-Z]", RegexOptions.ECMAScript).Success)
            {
                score++;
            }

            if (Regex.Match(password, @"[!,@,#,$,%,^,&,*,?,_,~,-,£,(,)]", RegexOptions.ECMAScript).Success)
            {
                score++;
            }

            return (PasswordScore)score;
        }

        private static bool IsPasswordInWeakList(string password)
        {
            foreach (string weakPassword in _weakPasswordList)
            {
                if (string.Compare(password,weakPassword,System.StringComparison.OrdinalIgnoreCase) == 0)
                {
                    return true;
                }

                if (PerformSubstitutions(weakPassword, password))
                {
                    return true;
                }
            }

            return false;
        }

        private static bool PerformSubstitutions(string weakPassword, string password)
        {
            char[] vowels =            { 'A', 'a', 'e', 'i', 'o', 's', 'S'};
            char[] vowelSubstitution = { '4', '@', '3', '1', '0', '$', '5' };

            ReplaceLettersWithSubStitutions(password,vowels, vowelSubstitution);

            if (string.Compare(ReplaceLettersWithSubStitutions(weakPassword, vowels, vowelSubstitution), password, System.StringComparison.OrdinalIgnoreCase) == 0)
            {
                return true;
            }

            char[] qwerty = { 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P' };
            char[] qwertySubstitution = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};

            if (string.Compare(ReplaceLettersWithSubStitutions(weakPassword, qwerty, qwertySubstitution), password, System.StringComparison.OrdinalIgnoreCase) == 0)
            {
                return true;
            }

            return false;
        }

        private static string ReplaceLettersWithSubStitutions(string password, char[] original, char[] substitution)
        {
            string newPassword = string.Empty;

            foreach (char c in password)
            {
                bool numberAdded = false;

                for (int q = 0; q < original.Length; q++)
                {
                    if (c == original[q])
                    {
                        newPassword = newPassword + substitution[q];
                        numberAdded = true;
                        break;
                    }
                }

                if (!numberAdded)
                {
                    newPassword = newPassword + c;
                }
            }

            return newPassword;
        }
    }
}

Here are some unit tests that exercise the code.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using HauntedHouseSoftware.SecureNotePad.DomainObjects;

namespace HauntedHouseSoftware.SecureNotePad.Tests.Unit.DomainObjects
{
    [TestClass]
    public class PasswordStrengthTests
    {
        [TestMethod]
        public void CheckStrengthReturnsEmptyForBlankPassword()
        {
            Assert.AreEqual(PasswordScore.Blank, PasswordStrength.CheckStrength(""));
        }

        [TestMethod]
        public void CheckStrengthReturnsVeryWeakForPasswordOfHello()
        {
            Assert.AreEqual(PasswordScore.VeryWeak, PasswordStrength.CheckStrength("Hello"));
        }

        [TestMethod]
        public void CheckStrengthReturnsWeakForPasswordOfHello2()
        {
            Assert.AreEqual(PasswordScore.Weak, PasswordStrength.CheckStrength("Hello2"));
        }

        [TestMethod]
        public void CheckStrengthReturnsMediumForPasswordOfHelloWorld()
        {
            Assert.AreEqual(PasswordScore.Medium, PasswordStrength.CheckStrength("HelloWorld"));
        }

        [TestMethod]
        public void CheckStrengthReturnsStrongForPasswordOfHelloWorld69()
        {
            Assert.AreEqual(PasswordScore.Strong, PasswordStrength.CheckStrength("HelloWorld69"));
        }

        [TestMethod]
        public void CheckStrengthReturnsVeryStrongForPasswordOfHelloWorld69WithExclaimationMark()
        {
            Assert.AreEqual(PasswordScore.VeryStrong, PasswordStrength.CheckStrength("HelloWorld69!"));
        }

        [TestMethod]
        public void CheckStrengthReturnsStrongForPasswordOf1234HelloWorld69()
        {
            Assert.AreEqual(PasswordScore.Strong, PasswordStrength.CheckStrength("1234HelloWorld69"));
        }

        [TestMethod]
        public void CheckStrengthReturnsVeryStrongForPasswordOf1234HelloWorld69WithExclaimationMark()
        {
            Assert.AreEqual(PasswordScore.VeryStrong, PasswordStrength.CheckStrength("!1234HelloWorld69"));
        }

        [TestMethod]
        public void CheckStrengthReturnsWeakForPasswordOfPasswordOnTheWeakPasswordList()
        {
            Assert.AreEqual(PasswordScore.Weak, PasswordStrength.CheckStrength("password"));
        }

        [TestMethod]
        public void CheckStrengthReturnsWeakForPasswordOfTrustNoOne1OnTheWeakPasswordList()
        {
            Assert.AreEqual(PasswordScore.Weak, PasswordStrength.CheckStrength("trustno1"));
        }

        [TestMethod]
        public void CheckStrengthReturnsWeakForPasswordOfTRUSTNO1OnTheWeakPasswordList()
        {
            Assert.AreEqual(PasswordScore.Weak, PasswordStrength.CheckStrength("TRUSTNO1"));
        }

        [TestMethod]
        public void CheckStrengthReturnsWeakForPasswordOfP455w9rdOnTheWeakPasswordList()
        {
            Assert.AreEqual(PasswordScore.Weak, PasswordStrength.CheckStrength("p455w0rd"));
        }

        [TestMethod]
        public void CheckStrengthReturnsWeakForPasswordOfTrustN01TheWeakPasswordList()
        {
            Assert.AreEqual(PasswordScore.Weak, PasswordStrength.CheckStrength("trustn01"));
        }

        [TestMethod]
        public void CheckStrengthReturnsWeakForPasswordOfW3lc0m3TheWeakPasswordList()
        {
            Assert.AreEqual(PasswordScore.Weak, PasswordStrength.CheckStrength("w3lc0m3"));
        }

        [TestMethod]
        public void CheckStrengthReturnsWeakForPasswordOfPasswordOnTheWeakPasswordListIncludingNonCharacters()
        {
            Assert.AreEqual(PasswordScore.Weak, PasswordStrength.CheckStrength("p@$$w0rd"));
        }
    }
}
Participate with Coding in the Trenches on Facebook

Participate with Coding in the Trenches on Facebook by Click the button above.

One thought on “Checking the Strength of a Password

  1. Torsten Zachert

    This checker has a gap. If I have a password of a length from 4 to 7 with only upper or lower cases, than the checker gives PasswordScore.Blank

    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