In this article I want to talk about a recent password strength checker that I build for my open source application 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 :

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("[email protected]$$w0rd")); } } }