We want a lookahead anchored at the string start for each required character type as well as the length requirement. Then a simple .*
to slurp it all up:
^(?=[0-9a-zA-Z#@\$\?]{8,}$)(?=[^a-z]*[a-z])(?=[^A-Z]*[A-Z])(?=[^0-9]*[0-9]).*
Explanation:
First off, I decided to avoid use of the lazy quantifier when matching required char types (e.g. one uppercase) for the following reasons:
- They are expensive.
- Different regex engines have different ways of signifying lazy. (A couple don't support it at all.)
- They aren't as common/familiar as the alternative.
So for efficiency, readability and "portability" I'm using the ^[^x]*[x]
construct.
Now breaking the rest down...
^
: Everything anchored to the start
(?=[0-9a-zA-Z#@\$\?]{8,}$)
: Lookahead with 8 or more of your allowed characters between start and end of string.
The next three use the same pattern: a lookahead matching zero or more of a char not matching a required char, then the required char. These are all anchored to the beginning so the effect is to allow a match of the required char at any position in the string:
(?=[^a-z]*[a-z])
: At least one lowercase.
(?=[^A-Z]*[A-Z])
: At least one uppercase.
(?=[^0-9]*[0-9])
: At least one digit.
.*
: Everything above is lookahead which doesn't consume anything so consume it all here. The first lookahead makes sure the entire string is valid chars so this is safe.
I make no claims about this being optimized (except for avoiding lazy quantifier). This is simply one of the easier forms to comprehend.
Note: the cause of the problem you observed with #@$?
is due to the lookahead not being anchored to the end of string. Any character will match after one of those four (and not necessarily just in the last position). Of course, you can't just add $
since that then crowds out valid characters. That's why I include all valid characters in the same lookahead.
.{8,}
meaning 'any character, eight or more times'. I think if you want to only permit those characters, it may be as simple as/[A-Za-z0-9#@$?]{8,}/
..{8,}$
at the end. Is this what you want ? This looks wrong to me.[#@$?]
(including the brackets or only what is whithin them?); 4) Order does not matter, as long as the 3 previous conditions are met. Am I right or I missed something?