Question

Question: How can I write this without putting in all the possible cases? If someone gets 4 password requirements one day it will be easy to put in but be very difficult to debug since there will be 4! different ways to work the middle.

How I have it in code:

[RegularExpression("^[A-Za-z0-9!@#$]*([A-Za-z][0-9][!@#$]|[0-9][!@#$][A-Za-z]|[!@#$][A-Za-z][0-9]|[!@#$][0-9][A-Za-z]|[0-9][A-Za-z][!@#$]|[A-Za-z][!@#$][0-9])[A-Za-z0-9!@#$]*$", ErrorMessage = "The Password value must contain at least one letter, one digit, and one special character.")]

For viewing convenience I broke it so no scrolling is required:

[RegularExpression("^[A-Za-z0-9!@#$]*
([A-Za-z][0-9][!@#$]|[0-9][!@#$][A-Za-z]|
[!@#$][A-Za-z][0-9]|[!@#$][0-9][A-Za-z]|
[0-9][A-Za-z][!@#$]|[A-Za-z][!@#$][0-9])
[A-Za-z0-9!@#$]*$",
ErrorMessage = "The Password value must contain at least one letter,
one digit, and one special character.")]

As you can see this is 3! possibilities from the middle () separated by | (OR) so from this you can see 4! possibilities would be very difficult to maintain.

Question Requirements: The basic thing I want this RegularExpression in C# to do is require (in no particular order) at least one letter, at least one number, at least one special character !, @, #, or $

Was it helpful?

Solution

One Regex

You can use zero-width positive lookahead assertions (see MSDN's "Regular Expression Language - Quick Reference" - in the Grouping Constructs section) to check for your ANDed password requirements - without having to explicitly cover all the orders in which the required password components may occur:

^(?=.*?[a-zA-Z])(?=.*?[0-9])(?=.*?[!@#$]).{3,}$

This works as follows:

  • (?=.*?[a-zA-Z]) - an alpha character found ahead
  • (?=.*?[0-9]) - and a number found ahead
  • (?=.*?[!@#$]) - and an allowed special character ahead
  • .{3,} - three or more characters in total

I have shared a regex fiddle based on this that you might find useful.

Property-Per-Criterion Password Checker

Per @LeffeBrune's comment, you should consider alternatively checking with a dedicated password-checker class that exposes a property per criterion.

For example, here is a quick & dirty PoC in LINQPad 4...

void Main()
{
    var passwords = new string[] {"password", "passw0rd", "passw0rd!", "123456", "!#@$"};

    foreach (var pw in passwords)
    {
        var checker = new PasswordChecker(pw);
        var isValid = checker.IsValid;

        Console.WriteLine("Password {0} is {1}valid.", pw, isValid ? "" : "in");

        if (!isValid)
        {
            Console.WriteLine("    Has alpha?  {0}", checker.HasAlpha ? "Yes." : "NO!");
            Console.WriteLine("    Has number?  {0}", checker.HasNumber ? "Yes." : "NO!");
            Console.WriteLine("    Has special?  {0}", checker.HasSpecial ? "Yes." : "NO!");
            Console.WriteLine("    Has length?  {0}", checker.HasLength ? "Yes." : "NO!");
        }
    }
}

public class PasswordChecker
{
    public const int MINIMUM_LENGTH = 3;
    private string _password;

    public PasswordChecker(string password)
    {
        _password = password;
    }

    public bool HasAlpha
    {
        get
        {
            return Regex.Match(_password, "(?=.*?[a-zA-Z])").Success;
        }
    }

    public bool HasNumber
    {
        get
        {
            return Regex.Match(_password, "(?=.*?[0-9])").Success;
        }
    }

    public bool HasSpecial
    {
        get
        {
            return Regex.Match(_password, "(?=.*?[!@#$])").Success;
        }
    }

    public bool HasLength
    {
        get
        {
            return _password.Length >= MINIMUM_LENGTH;
        }
    }

    public bool IsValid
    {
        get
        {
            return HasLength && HasAlpha && HasNumber && HasSpecial;
        }
    }
}

...that produces the following output:

Password password is invalid.
    Has alpha?  Yes.
    Has number?  NO!
    Has special?  NO!
    Has length?  Yes.
Password passw0rd is invalid.
    Has alpha?  Yes.
    Has number?  Yes.
    Has special?  NO!
    Has length?  Yes.
Password passw0rd! is valid.
Password 123456 is invalid.
    Has alpha?  NO!
    Has number?  Yes.
    Has special?  NO!
    Has length?  Yes.
Password !#@$ is invalid.
    Has alpha?  NO!
    Has number?  NO!
    Has special?  Yes.
    Has length?  Yes.

You could take this quick & dirty PoC much further of course, but the benefits of knowing what criterion or criteria failed are hopefully clear.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top