views:

358

answers:

3

Hello,

I've seen around the web the following regex

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

which 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 regex 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 chars then I wouldn't need the 8 characters length.

special characters include:

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

thanks in advance.

+7  A: 

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. :-/

Alan Moore
I agree, it's best to run separate checks not one magic regex. This way you can easily modify specific parts or add/remove them. Personally I don't think forcing a strong password is a good user experience, those JS based "password strength" bars are much better because you're both educating the user and mildly punishing them for using a bad password, but if they really want to use one, they can.
TravisO
ok ok .. you all persuade me .. I won't try the regex since it clearly is unreadable. thanks everyone!
Jason
@TravisO: Yeah, I like those password-strength bars, too.
Alan Moore
+3  A: 

If you really want to use a regular expression, try this:

(?=^(?:[^A-Z]*[A-Z]){2})(?=^(?:[^a-z]*[a-z]){2})(?=^(?:\D*\d){2})(?=^(?:\w*\W){2})^[A-Za-z\d\W]{8,}$

Some explanation:

  • (?=^(?:[^A-Z]*[A-Z]){2}) tests for two repetitions of [^A-Z]*[A-Z] which is a sequence of zero or more characters except uppercase letters followed by one uppercase letter
  • (?=^(?:[^a-z]*[a-z]){2}) (same as above with lowercase letters)
  • (?=^(?:\D*\d){2}) (same as above with digits)
  • (?=^(?:\w*\W){2}) (same as above with non-word characters, but you may change \W with a character class of whatever special characters you want)
  • ^[A-Za-z\d\W]{8,}$ tests the length of the whole string consisting only of character of the union of all other character classes.
Gumbo
Great answer with a nice explanation, I'm voting this up even though I don't recommend your answer. In this case I think it's becoming to unreadable, standard code would be a better choice, the only reason I would use regex because extreme performance was an issue and I needed to check millions of passwords.
TravisO
Actually, if performance were a factor, it would be another reason *not* to use a regex. Otherwise I agree: +1 for the definitive regex solution (in case you really have to go that route).
Alan Moore
+1  A: 

I have to agree with Alan. If the existing regex is so complicated, why try and do it in just one regex?

Just break it down into approachable simple steps. You have already done that.

Now write 4 regex to validate your parts, add basic logic to the 4 regex and measure the length of the string. Done.

Which would you rather debug, this:

(?=^(?:[^A-Z]*[A-Z]){2})(?=^(?:[^a-z]*[a-z]){2})(?=^(?:\D*\d){2})(?=^(?:\w*\W){2})^[A-Za-z\d\W]{8,}$ (which does not work btw...)

or this:

function valid_pass($candidate) {
   $r1='/[A-Z]/';  //Uppercase
   $r2='/[a-z]/';  //lowercase
   $r3='/[!@#$%^&*()-_=+{};:,<.>]/';  // whatever you mean by 'special char'
   $r4='/[0-9]/';  //numbers

   if(preg_match_all($r1,$candidate, $o)<2) return FALSE;

   if(preg_match_all($r2,$candidate, $o)<2) return FALSE;

   if(preg_match_all($r3,$candidate, $o)<2) return FALSE;

   if(preg_match_all($r4,$candidate, $o)<2) return FALSE;

   if(strlen($candidate)<8) return FALSE;

   return TRUE;
}

Why folks feel they have to write a regex that no one can understand just so they can do it in one go is beyond me...

drewk