I'm trying to write regex to validate the password for the given rule.
Passwords must be at least 8 characters in length and contain at least 3 of the following 4 types of characters:
- lower case letters (i.e. a-z)
- upper case letters (i.e. A-Z)
- numbers (i.e. 0-9)
- special characters (e.g. !@#$&*)
I was going through this discussion and found this really great answer over there.
Now I'm trying to write regex for the mentioned requirements and I came up with the solution like this
^(?=.*[A-Z])(?=.*[!@#$&*])(?=.*[0-9])(?=.*[a-z]).{8,}|
(?=.*[!@#$&*])(?=.*[0-9])(?=.*[a-z]).{8,}|
(?=.*[A-Z])(?=.*[0-9])(?=.*[a-z]).{8,}|
(?=.*[A-Z])(?=.*[!@#$&*])(?=.*[a-z]).{8,}|
(?=.*[A-Z])(?=.*[!@#$&*])(?=.*[0-9]).{8,}$
and it is working perfect see rubular but I want to optimize these regex and I'm not sure If there are any way to simplify this. Any suggestion will be appreciated.
4 Answers 4
Split the regex into smaller parts to check each rule individually.
Here's the code for JavaScript. The same simple logic can be used for Ruby or any other language.
// Check the length
if (str.length >= 8 && validate()) {
}
// Check if all the characters are present in string
function validate(string) {
// Initialize counter to zero
var counter = 0;
// On each test that is passed, increment the counter
if (/[a-z]/.test(string)) {
// If string contain at least one lowercase alphabet character
counter++;
}
if (/[A-Z]/.test(string)) {
counter++;
}
if (/[0-9]/.test(string)) {
counter++;
}
if (/[!@#$&*]/.test(string)) {
counter++;
}
// Check if at least three rules are satisfied
return counter >= 3;
}
@Tushar's idea isn't bad, but it is NOT optimal for re-usability and extensability.
What I propose is to filter out the rules that failed. This way, you can count how many failed a bit faster, instead of having a counter.
Even if it isn't that much faster, it is still easier to add rules.
function validate(string) {
if(string.length < 8)
{
return false;
}
var rules = [
/[a-z]/, //letters (lower-case)
/[A-Z]/, //letters (upper-case)
/\d/, //numbers (similar to /[0-9]/)
/[!@#$&*]/ //some symbols
];
return rules.filter(function(rule){
return rule.test(string);
}).length >= 3;
}
Yes, there is a magic number. I don't see any other way to improve it. If you want, you can do the oposite and count how many rules failed.
This may be a bit more flexible, if you want to allow some rules to fail.
Speed-wise, it is up to 5 times slower, but sometimes it is faster.
I guess is has something to do with the .filter
method.
Here's how I tested the speed:
function validate_mine(string) {
if(string.length < 8)
{
return false;
}
var rules = [
/[a-z]/, //letters (lower-case)
/[A-Z]/, //letters (upper-case)
/\d/, //numbers (similar to /[0-9]/)
/[!@#$&*]/ //some symbols
];
return rules.filter(function(rule){
return rule.test(string);
}).length >= 3;
}
function validate_Tusha(string) {
// Initialize counter to zero
var counter = 0;
// On each test that is passed, increment the counter
if (/[a-z]/.test(string)) {
// If string contain at least one lowercase alphabet character
counter++;
}
if (/[A-Z]/.test(string)) {
counter++;
}
if (/[0-9]/.test(string)) {
counter++;
}
if (/[!@#$&*]/.test(string)) {
counter++;
}
// Check if at least three rules are satisfied
return counter >= 3;
}
console.time('validate_mine');
[
'test it',
'Me want this!',
'Th1s_is_cool',
'$tuff!_should-be-fast',
'now! run it!'
].map(validate_mine);
console.timeEnd('validate_mine');
console.time('validate_Tusha');
[
'test it',
'Me want this!',
'Th1s_is_cool',
'$tuff!_should-be-fast',
'now! run it!'
].map(validate_Tusha);
console.timeEnd('validate_Tusha');
If you want to make something extremelly extensible, one could follow @Vld's idea and pass a list of rules to a function, and an optional parameter to relax how many rules can be missed.
Something similar to this:
function validate(string, rules, min_pass) {
'use strict';
if(min_pass < 0 || min_pass > rules.length)
{
throw new RangeError('min_pass should be between 0 and ' + rules.length);
}
return rules.filter(function(rule){
if(rule instanceof Function)
{
return !!rule.bind(string)(string);
}
else if(rule instanceof RegExp)
{
return rule.test(string);
}
return false;
}).length >= min_pass;
}
This accepts 3 parameters:
string
: The string to be tested,rules
: An array of regular expressions or functions,min_pass
: Minimum number of rules to pass.
To obtain the results you want, using this new method, you could do like this:
validate(
'string here',
[
/[a-b]/,
/[A-B]/,
/\d/,
/[!@#$&*]/
],
3
);
You still have to validate the length before, since I don't have a way to say that a rule is important or something.
The formatting still isn't optimal, but you get the idea.
Also, this is not a good way to check security. With your rules, a password like Passw0rd!
is valid, but we all know how crappy that is. Any password cracker worth it's salt will have rules to try these permutations. Heck, it may even be the one of the very first words in the dictionary list!
This will leave you prone to dictionary-based attacks.
Just make sure you use a good and unique salt, with a strong hash (like SHA256) instead of relying on this type of rules to check password strength.
This fails for passwords like ñó wÿn fõr ýôü
, which may be far more secure than some of the passwords you validate.
-
\$\begingroup\$ But the problem is that you need to have at least 3 out of 4. Doesn't that solution require all rules to match? Also, you aren't showing separate uppercase and lowercase rules, whereas they need to be distinct. \$\endgroup\$VLAZ– VLAZ2016年08月01日 16:38:16 +00:00Commented Aug 1, 2016 at 16:38
-
\$\begingroup\$ @Vld You mean, the first rule? It matches both upper and/or lower. \$\endgroup\$Ismael Miguel– Ismael Miguel2016年08月01日 16:39:44 +00:00Commented Aug 1, 2016 at 16:39
-
\$\begingroup\$ Yes. That's the problem.
password123
andPassword123
are different - the latter should be valid according to OP's requirements. With your validation it won't be.Password_
should also be valid. WhilePassword_123
andpassword_123
would be valid with yours, it's incidental, not explicitly checking for the requirements. \$\endgroup\$VLAZ– VLAZ2016年08月01日 16:45:56 +00:00Commented Aug 1, 2016 at 16:45 -
\$\begingroup\$ @Vld Give me 2 minutes \$\endgroup\$Ismael Miguel– Ismael Miguel2016年08月01日 16:47:33 +00:00Commented Aug 1, 2016 at 16:47
-
\$\begingroup\$ @Vld What do you think now? I hope it is better now. \$\endgroup\$Ismael Miguel– Ismael Miguel2016年08月01日 16:54:52 +00:00Commented Aug 1, 2016 at 16:54
Honestly, I think regex is the wrong approach here. Just go with regular form validation code - take the value, validate its length, set a counter to 0, and then for each requirement that it meets, increment the counter. Then check if it meets at least 3 by checking the counter.
The resulting code will be understandable for any programmer that comes across the code. It will be possible to easily change the rules to allow other special characters, to disallow certain common passwords, to change the length...
Your long regex is, whilst properly formatted, a real pain to maintain. You can still use the individual sub-expressions in your validation code, but it'll be a lot more readable.
No.
(削除) This is not a regular grammar. (削除ここまで) Regular expressions are not the right tool for this job.
Check string length and for the existence of character code points within each of the necessary ranges.
Better still, follow the other suggestions to push for a more legitimate password complexity algorithm.
I'll let someone edit in the appropriate and obligatory xkcd and stock overflow references.
-
2\$\begingroup\$ It's a regular grammar, though the theoretical regular expression is rather complex. Anyway, it's true that regular expression is too clunky for this use case. \$\endgroup\$nhahtdh– nhahtdh2016年08月02日 03:03:14 +00:00Commented Aug 2, 2016 at 3:03
-
\$\begingroup\$ @nhahtdh I'll take your word for it. Procedural validation is straight forward enough I can't force my brain to try a regex approach. \$\endgroup\$psaxton– psaxton2016年08月02日 03:32:06 +00:00Commented Aug 2, 2016 at 3:32
-
1\$\begingroup\$ It's a very regular grammar. If you think about it in a straightforward way - you could create a RegExp that does
|
between all the different orders of 3 of the 4 requirements (with anything in between and after) and if one of those matches the regexp matches. Writing a regexp is easy but it's clearly not a good idea. A good way to think about non-regular grammars is that they require some sort of "memory" or the ability to "keep track" of an unbounded amount of things. For any bounded amount (3 in this case) the grammar is regular. \$\endgroup\$Benjamin Gruenbaum– Benjamin Gruenbaum2016年08月02日 06:31:42 +00:00Commented Aug 2, 2016 at 6:31
Password1
\$\endgroup\$