61

I have the following criteria for creating a regular expression for a password that conforms to the following rules:

  1. The password must be 8 characters long (this I can do :-)).

The password must then contain characters from at least 3 of the following 4 rules:

  1. Upper case
  2. Lower case
  3. Numbers
  4. Non-alpha numeric

I can make the expression match ALL of those rules with the following expression:

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

But I am struggling with how to do this in such a way that it only needs to solve any 3 of the 4 rules.

Can anyone help me out with this?

Simon East
  • 55,742
  • 17
  • 139
  • 133
dagda1
  • 26,856
  • 59
  • 237
  • 450
  • 7
    I have to say that this is one of those cases when I think your code would probably be much more readable if you had 4 different regexs instead. And then maybe had a count and for each of the regex that matches you increment the count and then at the end you just check if the count is 3 or higher. – Hans Olsson Aug 12 '10 at 10:56
  • Which programming language are you using? – kennytm Aug 12 '10 at 10:57

4 Answers4

141

Don't use one regex to check it then.

if (password.length < 8)
  alert("bad password");
var hasUpperCase = /[A-Z]/.test(password);
var hasLowerCase = /[a-z]/.test(password);
var hasNumbers = /\d/.test(password);
var hasNonalphas = /\W/.test(password);
if (hasUpperCase + hasLowerCase + hasNumbers + hasNonalphas < 3)
  alert("bad password");

If you must use a single regex:

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

This regex is not optimized for efficiency. It is constructed by A·B·C + A·B·D + A·C·D + B·C·D with some factorization. Breakdown:

^
(?:
    (?=.*[a-z])       # 1. there is a lower-case letter ahead,
    (?:               #    and
        (?=.*[A-Z])   #     1.a.i) there is also an upper-case letter, and
        (?=.*[\d\W])  #     1.a.ii) a number (\d) or symbol (\W),
    |                 #    or
        (?=.*\W)      #     1.b.i) there is a symbol, and
        (?=.*\d)      #     1.b.ii) a number ahead
    )
|                     # OR
    (?=.*\W)          # 2.a) there is a symbol, and
    (?=.*[A-Z])       # 2.b) an upper-case letter, and
    (?=.*\d)          # 2.c) a number ahead.
)
.{8,}                 # the password must be at least 8 characters long.
$
Wtower
  • 18,848
  • 11
  • 103
  • 80
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
9

You could write a really sophisticated regex to do that. Instead, I’d suggest writing four distinct regexes, one for each rule, and testing them one by one, counting how many of them matched. If three out of four did, accept the password.

scy
  • 7,132
  • 2
  • 27
  • 35
  • 8
    The huge benefit of this over the other solutions, is that you'll actually be able to read it, reason about it and maintain it five years down the line. – Cobus Kruger Oct 19 '18 at 18:56
6

You can use the following Regex:

(^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).*$)?(^(?=.*\d)(?=.*[a-z])(?=.*[@#$%^&+=]).*$)?(^(?=.*\d)(?=.*[A-Z])(?=.*[@#$%^&+=]).*$)?(^(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=]).*$)?

With a password minimum length of 8 and max length 32 you can use the following Regex:

(^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,32}$)?(^(?=.*\d)(?=.*[a-z])(?=.*[@#$%^&+=]).{8,32}$)?(^(?=.*\d)(?=.*[A-Z])(?=.*[@#$%^&+=]).{8,32}$)?(^(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=]).{8,32}$)?
sth
  • 222,467
  • 53
  • 283
  • 367
ajithparamban
  • 2,833
  • 3
  • 17
  • 14
  • Out of four you need to take 3 at time and if any of it matches the password pattern will be validated. – ajithparamban Oct 18 '12 at 23:46
  • 4
    Why set a maximum password length? – evolutionxbox Dec 01 '14 at 11:24
  • Is there reason *not* to have max password length? This article ([source](https://www.thepolyglotdeveloper.com/2015/05/use-regex-to-test-password-strength-in-javascript)) doesn't have max length, is it valid and strong? `var strongRegex = new RegExp("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*])(?=.{8,})");`. It enforces: at least 1 lower, at least 1 upper, at least 1 number, atleast 1 special char, at least 8 characters length. Interestingly, even with these rules, [howsecureismypassword](https://howsecureismypassword.net) says it would take 9 hours to crack `aX1@a610`. – user1063287 May 11 '18 at 09:51
  • Just noticed that ^ (`strongRegex.test(string);`) returns true if there is a space in a string, not sure if that is problematic or not. – user1063287 May 11 '18 at 09:58
1

Id suggest doing the checks seperately, and then just totalling up how many match.

(I'd also not use a regex in any of them, but thats just my personal POV - namely that they hinder readability and are generally write-once code)

pauljwilliams
  • 19,079
  • 3
  • 51
  • 79