Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Help with password complexity regex

I'm using the following regex to validate password complexity:

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

In a nutshell: 2 lowercase, 2 uppercase, 2 numbers, min length is 6 and max length is 12.

It works perfectly, except for the maximum length, when I'm using a minimum length as well.

For example:

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

This correctly requires a minimum length of 6!

And this:

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

Correctly requires a maximum length of 12.

However, when I pair them together as in the first example, it just doesn't work!!

What gives? Thanks!

like image 928
Alex Weber Avatar asked Dec 13 '22 22:12

Alex Weber


1 Answers

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.

like image 176
cletus Avatar answered Dec 27 '22 02:12

cletus