正则表达式用于强密码,带有两个重复的数字

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])/
  1. I've removed all beginning and ending string anchors, except for the length match. You only care what the string contains, you don't care what shows up before or after that. As a consequence, I removed the entire ^.*$ clause at the end, which essentially means "match anything or nothing".
  2. I removed the upper limit on the length requirement. I'm always annoyed when I'm told my passwords are too long, as I can remember them perfectly well and if you force me to use something shorter then it's going to be harder for me to remember, not easier. This one isn't essential if you feel it's important, I just hate the practice of having a restrictive upper bound on the password length. I also upped the minimum length to 8. Just a subjective judgement of safety.
  3. I split up the lower case and upper case letter matches, so they'd be required to have one of each. Again, not essential, and perhaps a little too much to require all 4 different kinds of characters, but there you go.
  4. I used the character class negation from Petah's answer for special characters, as that's far easier to read and it captures everything without you having to remember to include it.
  5. I removed the repeated characters negative lookahead since that doesn't seem to be what you want, and I don't see that you really gain by doing that anyway.

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;
    }

}