Hi I have following regular expression
$str = np00@1;
$special = '!@#$%*-_=+.';
preg_match('/^(?=^.{6,12}$)(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[' . $special . '])(?!.*?(.)\1{1,})^.*$/', $str));
This expression works on test@123
, test#123
and so on but not works with np00@1
, and on te11@22
it not works.
You've got some strangeness going on after your special characters lookahead.
/^(?=^.{6,12}$)(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[' . $special . '])(?!.*?(.)\1{1,})^.*$/
... so, first of all the negative lookahead:
(?!.*?(.)\1{1,})
I believe what you're going for here is to disallow any repeat characters. This is why it's failing on np00@1
and te11@22
. They repeat the characters 00 and 11 respectively. I'm not sure what you need the {1,} for, since it's a negative match and you're not saving the match for a backreference later on, just a single match is all you need and you don't really care if it matches more than that. book
fails just as thoroughly as bookkeeper
.
And then, what's the ^.*$ for at the end? That one makes no sense to me at all, especially since you've got the ^ at the beginning, neither of which are strictly necessary. If I were to write this regex, it would look like the following, with notes on my changes afterwards:
/(?=^.{8,}$)(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9])/
Some further comments and critiques:
Using a single regex to test password complexity, while cool, is far less useful than a more granular approach like Petah's answer. Testing each stage individually allows you to provide more specific feedback to your user about why their password failed and, as in his method, allows you to be more flexible about just how complex you require the password to be. Also, be careful about being too strict. The more onerous the requirements you place on your users, the more likely they are to subvert your efforts by choosing obvious patterns and writing things down in obvious places. Is the content you're trying to protect really worth someone going to the effort to brute force passwords? Usually a minimum length requirement is going to be far more effective than a high complexity requirement if you're not storing financial or otherwise sensitive information. Even if you're a webstore and charging credit cards, if you're not storing those credit cards so the user could get them back out again, you're a low priority target for having your users accounts brute forced. If this is for a website backend, then complexity requirements make a little more sense. In most user facing scenarios it probably makes sense to require at least a letter and number, but beyond that leave it up to the user.
Here is an exert from my PasswordPolicy class, which might be of help:
class PasswordPolicy {
/**
* @var int The minimum length of a password
*/
public $length;
/**
* @var int Minimum amount of character variance, e.g. at
* least 3 of the following:
* - Upper case characters
* - Lower case characters
* - Numbers
* - Symbols ~@#$%^&*+-/()[]{}|\<>,.?;:'"_=
*/
public $variance;
public function __construct($length = 9, $variance = 3) {
$this->length = $length;
$this->variance = $variance;
}
public function validate($password) {
$errors = array();
if (strlen($password) < $this->length) {
$errors[] = "The password must be at least $this->length characters long";
}
// Lower case
$variance = preg_match('/[a-z]/', $password) > 0 ? 1 : 0;
// Upper case
$variance += preg_match('/[A-Z]/', $password) > 0 ? 1 : 0;
// Numbers
$variance += preg_match('/[0-9]/', $password) > 0 ? 1 : 0;
// Symbols
$variance += preg_match('/[^a-zA-Z0-9]/', $password) > 0 ? 1 : 0;
if ($variance < $this->variance) {
$errors[] = "The password must contain at least $this->variance of the " .
"following types of characters: lower case, upper case, " .
"numeric, and/or special symbols (e.g. !@#$%^&*)";
}
return $errors;
}
}