0

I am new to regular expressions and was wondering how to write a regular expression that would pass if some of the statements in the expression pass.

For example I have this regex

^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*(_|[^\w])).+$

which matches if the string has

  • 1 lowercase character,
  • 1 uppercase character,
  • 1 digit,
  • 1 symbol.

Is it possible to have a regular expression that would pass if at least 3 of the 4 conditions were true in any order?

(i.e. would pass if string had 1 lower, 1 upper, 1 symbol, or 1 upper, 1 digit, 1 symbol, etc.)

Any help is appreciated!

hjpotter92
  • 78,589
  • 36
  • 144
  • 183
James
  • 557
  • 1
  • 12
  • 29

1 Answers1

1

The best approach I can propose you is to capture every type of characters in a capture group and make sure that at least 3/4 capture groups have a value (if the group can't match anything, it should be an empty string).

^(?:([a-z])|([A-Z])|(\d)|(_|[^\w]))+$

You can also add a positive lookahead to make sure that the password have the required length (by example 8 to 32 characters).

^(?=.{8,32}$)(?:([a-z])|([A-Z])|(\d)|(_|[^\w]))+$

Edit: ([\W_]) is the equivalent of (_|[^\w]). Putting the "W" in upper case reverse it sense (match all the non-word characters). Moreover, using a single character class is faster than alternation (more details here)

If you are willing to use javascript, I adapted a function presented in "Regular Expression cookbook second edition" for the needs of my website:

var PASSWORD_RANKING = {

    TOO_SHORT: 0,
    WEAK: 1,
    MEDIUM: 2,
    STRONG: 3,
    VERY_STRONG: 4
};

/**
* Take a password and returns it's ranking
* based of the strength of the password (length, numeric character, alphabetic character, special character, etc.)
*
* @param password String
* @param minLength Int
*
* @return Int
*/ 
function rankPassword(password, minLength){

    var rank = PASSWORD_RANKING.TOO_SHORT;
    var score = 0;

    if (typeof minLength !== 'number' || minLength < 6){

        minLength = 8;
    }

    if (typeof password === 'string' && password.length >= minLength){

        if (/[A-Z]/.test(password)){ score++;}

        if (/[a-z]/.test(password)){ score++;}

        if (/[0-9]/.test(password)){ score++;}

        if (/[_~!@.#$%^&]/.test(password)){ score++;}

        score += Math.floor((password.length - minLength) / 2);

        if (score < 3){

            rank = PASSWORD_RANKING.WEAK;
        }
        else if (score < 4){

            rank = PASSWORD_RANKING.MEDIUM;
        }
        else if (score < 6){

            rank = PASSWORD_RANKING.STRONG;
        }
        else { 

            rank = PASSWORD_RANKING.VERY_STRONG;
        }
    }

    return rank;
}

The section 4.19 present many regexes to enforce password strength. You can see all the code samples online : http://examples.oreilly.com/0636920023630/Regex_Cookbook_2_Code_Samples.html

Community
  • 1
  • 1