Asked  7 Months ago    Answers:  5   Viewed   46 times

I've seen the following regular expression around the web.

(?=^.{8,}$)((?=.*d)|(?=.*W+))(?![.n])(?=.*[A-Z])(?=.*[a-z]).*$

It validates only if the string:

   * contain at least (1) upper case letter
   * contain at least (1) lower case letter
   * contain at least (1) number or special character
   * contain at least (8) characters in length

I'd like to know how to convert this regular expression so that it checks the string to

* contain at least (2) upper case letter
* contain at least (2) lower case letter
* contain at least (2) digits
* contain at least (2) special character
* contain at least (8) characters in length

Well, if it contains at least 2 upper,lower,digits and special characters then I wouldn't need the 8 characters length.

Special characters include:

`~!@#$%^&*()_-+=[]|{};:'".,/<>?

 Answers

12

The best way to adapt that regex is to chuck it out and write some code instead. The required regex would be so long and complicated, you wouldn't be able to read it two hours after you wrote it. The equivalent PHP code will be tedious, but at least you'll be able understand what you wrote.

This isn't meant as a slam on you, by the way. Regexes are just barely suitable for password-strength validation in most cases, but your requirements are more complicated than usual, and it's just not worth it. Also, that regex you posted is crap. Never trust regexes you find floating around the web. Or any code, for that matter. Or, heck, anything. :-/

Wednesday, March 31, 2021
 
DaveRandom
answered 7 Months ago
59

You want:

/^(?=.{6,12}$)...

What you're doing is saying: find me any sequence of characters that is followed by:

  • 6-12 characters
  • another sequence of characters that is followed by 2 digits
  • another sequence of characters that is followed by 2 uppercase letters
  • another sequence of characters that is followed by 2 lowercase letters

And all that is followed by yet another sequence of characters. That's why the maximum length isn't working because 30 characters followed by 00AAaa and another 30 characters will pass.

Also what you're doing is forcing two numbers together. To be less stringent than that but requiring at least two numbers anywhere in the string:

/^(?=.{6,12}$)(?=(.*?d){2})(?=(.*?[A-Z]){2})(?=(.*?[a-z]){2})/

Lastly you'll note that I'm using non-greedy expressions (.*?). That will avoid a lot of backtracking and for this kind of validation is what you should generally use. The difference between:

(.*d){2}

and

(.*?d){2}

Is that the first will grab all the characters with .* and then look for a digit. It won't find one because it will be at the end of the string so it will backtrack one characters and then look for a digit. If it's not a digit it will keep backtracking until it finds one. After it does it will match that whole expression a second time, which will trigger even more backtracking.

That's what greedy wildcards means.

The second version will pass on zero characters to .*? and look for a digit. If it's not a digit .*? will grab another characters and then look for a digit and so on. Particularly on long search strings this can be orders of magnitude faster. On a short password it almost certainly won't make a difference but it's a good habit to get into of knowing how the regex matcher works and writing the best regex you can.

That being said, this is probably an example of being too clever for your own good. If a password is rejected as not satisfying those conditions, how do you determine which one failed in order to give feedback to the user about what to fix? A programmatic solution is, in practice, probably preferable.

Wednesday, March 31, 2021
 
alioygur
answered 7 Months ago
81

I find that doing it in one big regex is a bit of a code maintenance nightmare. Splitting it up is far easier to figure out for someone else looking at your code, and it allows you to give more specific error messages as well.

$uppercase = preg_match('@[A-Z]@', $password);
$lowercase = preg_match('@[a-z]@', $password);
$number    = preg_match('@[0-9]@', $password);

if(!$uppercase || !$lowercase || !$number || strlen($password) < 8) {
  // tell the user something went wrong
}
Monday, June 21, 2021
 
SJain
answered 4 Months ago
81

In other words, you want a password that doesn't just contain one "class" of characters. Then you can use

^(?![a-z]*$)(?![A-Z]*$)(?!d*$)(?!p{P}*$)(?![^a-zA-Zdp{P}]*$).{6,}$

Explanation:

^           # Start of string
(?![a-z]*$) # Assert that it doesn't just contain lowercase alphas
(?![A-Z]*$) # Assert that it doesn't just contain uppercase alphas
(?!d*$)    # Assert that it doesn't just contain digits
(?!p{P}*$) # Assert that it doesn't just contain punctuation
(?![^a-zA-Zdp{P}]*$) # or the inverse of the above
.{6,}       # Match at least six characters
$           # End of string
Wednesday, August 4, 2021
 
Ramacciotti
answered 3 Months ago
48

I think you'll have to check each group independently. Pseudo-code:

bool[] array = {};
array[0] = pwd.match(/[A-Z]/);
array[1] = pwd.match(/[a-z]/);
array[2] = pwd.match(/d/);
array[3] = pwd.match(/[!_.-]/);

int sum = 0;
for (int i=0; i<array.length; i++) {
    sum += array[i] ? 1 : 0;
}

switch (sum) {
    case 0: print("weird..."); break;
    case 1: print("weak"); break;
    case 2: print("ok"); break;
    case 3: print("strong"); break;
    case 4: print("awesome"); break;
    default: print("weird..."); break;
}
Wednesday, August 11, 2021
 
Byron Whitlock
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :