Reputation: 740
I am a newbie to RegEx and have done a lot of searching already but have not found anything specific.
I am writing a regular expression that validates a password string. The acceptable string must have at least 3 of 4 character types: digits, lowercase, uppercase, special char[<+$*)], but must not include another special set of characters(|;{}).
I got an idea regarding the inclusion(that is if it is the right way).
It looks like this:
^((a-z | A-Z |\d)(a-z|A-Z|[<+$*])(a-z|[<+$*]|\d)(A-Z|[<+$*]|\d)).*$
How do I ensure that user does not enter special chars(|;{}) This is what I tried with the exclusion string:
^(?:(?![|;{}]).)*$
I have tried a bit of tricks to combine the two in a single regEx but can't get it to work.
Any input on how to do this right?
Upvotes: 3
Views: 1501
Reputation: 93765
Don't try to do it all in one regex. Make two different checks.
Say you're working in Perl (since you didn't specify language):
$valid_pw =
( $pw =~ /^((a-z | A-Z |\d)(a-z|A-Z|[<+$*])(a-z|[<+$*]|\d)(A-Z|[<+$*]|\d)).*$/ ) &&
( $pw !~ /\|;{}/ );
You're saying "If the PW matches all the inclusion rules, and the PW does NOT match any of the excluded characters, then the password is valid."
Look how much clearer that is than something like @Jerry's response above of:
^(?![^a-zA-Z]*$|[^a-z0-9]*$|[^a-z<+$*]*$|[^A-Z0-9]*$|[^A-Z<+$*]*$|[^0-9<+$*]*$|.*[|;{}]).*$
I don't doubt that Jerry's version works, but which one do you want to maintain?
In fact, you could break it down even further and be extremely clear:
my $cases_matched = 0;
$cases_matched++ if ( $pw =~ /\d/ ); # digits
$cases_matched++ if ( $pw =~ /[a-z]/ ); # lowercase
$cases_matched++ if ( $pw =~ /[A-Z]/ ); # uppercase
$cases_matched++ if ( $pw =~ /<\+\$\*/ ); # special characters
my $is_valid = ($cases_matched >= 3) && ($pw !~ /\|;{}/); # At least 3, and none forbidden.
Sure, that takes up 6 lines instead of one, but in a year when you go back and have to add a new rule, or figure out what the code does, you'll be glad you wrote it that way.
Just because you can do it in one regex doesn't mean you should.
Upvotes: 9
Reputation: 71578
This is for accepting only the characters you mentioned:
^(?:(?=.*[0-9])(?=.*[a-z])(?=.*[<+$*)])|(?=.*[a-z])(?=.*[<+$*)])(?=.*[A-Z])|(?=.*[0-9])(?=.*[A-Z])(?=.*[<+$*)])|(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]))[0-9A-Za-z<+$*)]+$
And this one for all the characters you mentioned, and any special characters except |;{}
.
^(?:(?=.*[0-9])(?=.*[a-z])(?=.*[<+$*)])|(?=.*[a-z])(?=.*[<+$*)])(?=.*[A-Z])|(?=.*[0-9])(?=.*[A-Z])(?=.*[<+$*)])|(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]))(?!.*[|;{}].*$).+$
(One difference is that the first regex doesn't accept the special char @
but the second does).
I have also used +
since passwords logically can't be 0 width.
However, it's quite long, longer than F.J's regex, oh well. That's because I'm using positive lookaheads, which require more checks.
Upvotes: 1
Reputation: 208625
Your current regex will not work for enforcing the at least 3 of 4 requirement. Using regex for this gets pretty complicated, but in my opinion the best way to do this is to use a negative lookahead that contains all of the failure cases, so that the entire match will fail if any of the negative cases are met. In this case the "at least 3 of 4" requirement can also be described as "fail if any 2 groups are not found". This also makes it very easy to add the final requirement to ensure that no characters from [|;{}]
are found:
^ # beginning of string anchor
(?! # fail if
[^a-zA-Z]*$ # no [a-z] or [A-Z] anywhere
| # OR
[^a-z0-9]*$ # no [a-z] or [0-9] anywhere
| # OR
[^a-z<+$*]*$ # no [a-z] or [<+$*] anywhere
| # OR
[^A-Z0-9]*$ # no [A-Z] or [0-9] anywhere
| # OR
[^A-Z<+$*]*$ # no [A-Z] or [<+$*] anywhere
| # OR
[^0-9<+$*]*$ # no [0-9] or [<+$*] anywhere
| # OR
.*[|;{}] # a character from [|;{}] exists
)
.*$ # made it past the negative cases, match the entire string
Here it is as a single line:
^(?![^a-zA-Z]*$|[^a-z0-9]*$|[^a-z<+$*]*$|[^A-Z0-9]*$|[^A-Z<+$*]*$|[^0-9<+$*]*$|.*[|;{}]).*$
Example: http://rubular.com/r/4YV6Aj0vqh
Upvotes: 2