ChatGPT解决这个技术问题 Extra ChatGPT

Regular Expression for password validation

I currently use this regular expression to check if a string conforms to a few conditions.

The conditions are string must be between 8 and 15 characters long. string must contain at least one number. string must contain at least one uppercase letter. string must contain at least one lowercase letter.

(?!^[0-9]*$)(?!^[a-zA-Z]*$)^([a-zA-Z0-9]{8,15})$

It works for the most part, but it does not allow special character. Any help modifying this regex to allow special character is much appreciated.

I wish people would stop enforcing silly constraints on passwords. It's irritating, and makes them less secure.
@Oli If you do not enforce restrictions like this then most users tend to create very simple passwords that are very easy to break :).
@Oli I generally agree, but I do accept the 8 character minimum as a valid constraint. The whole upper/lower/number/special/firstbornchild restrictions are kind of over the top.
@desi: But by enforcing this, you end up with people creating passwords that they can't memorise, so they end up writing them down somewhere.
@Oli: Social engineering always is a problem. In the case of no constraints on the password, you can put relevant names etc. on the top of your list of passwords to check, in the other case, you at least need to have physical access to that persons desk, purse etc. to find the piece of paper with the password. All in all, passwords with reasonable constraints - e.g. several character classes - are more secure than those without the constraints. I agree, that they also are not perfect, but really, what is?

J
Justin Morgan

There seems to be a lot of confusion here. The answers I see so far don't correctly enforce the 1+ number/1+ lowercase/1+ uppercase rule, meaning that passwords like abc123, 123XYZ, or AB*&^# would still be accepted. Preventing all-lowercase, all-caps, or all-digits is not enough; you have to enforce the presence of at least one of each.

Try the following:

^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,15}$

If you also want to require at least one special character (which is probably a good idea), try this:

^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).{8,15}$

The .{8,15} can be made more restrictive if you wish (for example, you could change it to \S{8,15} to disallow whitespace), but remember that doing so will reduce the strength of your password scheme.

I've tested this pattern and it works as expected. Tested on ReFiddle here: http://refiddle.com/110

Edit: One small note, the easiest way to do this is with 3 separate regexes and the string's Length property. It's also easier to read and maintain, so do it that way if you have the option. If this is for validation rules in markup, though, you're probably stuck with a single regex.


The starting and trailing / are just for emphasis that this is regex code. You shouldn't include them if you're using (for example) .NET.
Update: Removed the forward slashes to avoid confusion, since this question is about .NET.
.{8,15} can be made as .{8,} if you want to have more than 8 characters.
The first one you said to try allows characters like full stop, etc. Kindly include one that only allows for alphanumeric (i.e. numbers, lower case and upper case alphabets).
Why we need ^ and $? I test the regex without ^$ works fine.
N
Nicholas Carey

Is a regular expression an easier/better way to enforce a simple constraint than the more obvious way?

static bool ValidatePassword( string password )
{
  const int MIN_LENGTH =  8 ;
  const int MAX_LENGTH = 15 ;

  if ( password == null ) throw new ArgumentNullException() ;

  bool meetsLengthRequirements = password.Length >= MIN_LENGTH && password.Length <= MAX_LENGTH ;
  bool hasUpperCaseLetter      = false ;
  bool hasLowerCaseLetter      = false ;
  bool hasDecimalDigit         = false ;

  if ( meetsLengthRequirements )
  {
    foreach (char c in password )
    {
      if      ( char.IsUpper(c) ) hasUpperCaseLetter = true ;
      else if ( char.IsLower(c) ) hasLowerCaseLetter = true ;
      else if ( char.IsDigit(c) ) hasDecimalDigit    = true ;
    }
  }

  bool isValid = meetsLengthRequirements
              && hasUpperCaseLetter
              && hasLowerCaseLetter
              && hasDecimalDigit
              ;
  return isValid ;

}

Which do you think that maintenance programmer 3 years from now who needs to modify the constraint will have an easier time understanding?


We do something like this in the back end, but the regex is being used in the front end on a asp.net RegularExpressionValidator, so that we do not have to post back each time the user makes a mistake.
The clientside javascript for this is about as complicated as the above.
You can put if (hasUpperCaseLetter && hasLowerCaseLetter && hasDecimalDigit) return true; inside the foreach and save a lot of unnecessary iteration.
@pedram - To clarify, I'm not talking about replacing the contents of the foreach. I'm saying add that line as the last step in the loop. That way, as soon as all the conditions are all true, we can immediately break out of the loop and stop iterating. Once you've found an uppercase letter, a lowercase letter, and a number, there's no need to continue checking the other characters. Then after the loop, you can simply put return false, because you checked the entire string and didn't find everything you were looking for.
okay @JustinMorgan now I got your point. Thanks for giving your time to explain it.
P
Pablo Alexis Domínguez Grau

You may try this method:

    private bool ValidatePassword(string password, out string ErrorMessage)
        {
            var input = password;
            ErrorMessage = string.Empty;

            if (string.IsNullOrWhiteSpace(input))
            {
                throw new Exception("Password should not be empty");
            }

            var hasNumber = new Regex(@"[0-9]+");
            var hasUpperChar = new Regex(@"[A-Z]+");
            var hasMiniMaxChars = new Regex(@".{8,15}");
            var hasLowerChar = new Regex(@"[a-z]+");
            var hasSymbols = new Regex(@"[!@#$%^&*()_+=\[{\]};:<>|./?,-]");

            if (!hasLowerChar.IsMatch(input))
            {
                ErrorMessage = "Password should contain at least one lower case letter.";
                return false;
            }
            else if (!hasUpperChar.IsMatch(input))
            {
                ErrorMessage = "Password should contain at least one upper case letter.";
                return false;
            }
            else if (!hasMiniMaxChars.IsMatch(input))
            {
                ErrorMessage = "Password should not be lesser than 8 or greater than 15 characters.";
                return false;
            }
            else if (!hasNumber.IsMatch(input))
            {
                ErrorMessage = "Password should contain at least one numeric value.";
                return false;
            }

            else if (!hasSymbols.IsMatch(input))
            {
                ErrorMessage = "Password should contain at least one special case character.";
                return false;
            }
            else
            {
                return true;
            }
        }

.{8,15} will still match a 16 character password
You wrote regex hasMiniMaxChars - ".{8,15}" for checking password character length, but error message that you wrote "Password should not be less than or greater than 12 characters".
how to allow only those characters that we are checking not others like other special characters or letters from other languages?
@Anurag How can I use this method inside the Model?
@Akhtubir This is a method you pass your password value to the method and wait for the results,
M
M Hanif

Update to Justin answer above. if you want to use it using Data Annotation in MVC you can do as follow

[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).{8,15}$", ErrorMessage = "Password must be between 6 and 20 characters and contain one uppercase letter, one lowercase letter, one digit and one special character.")]

A
Alex K.

I would check them one-by-one; i.e. look for a number \d+, then if that fails you can tell the user they need to add a digit. This avoids returning an "Invalid" error without hinting to the user whats wrong with it.


m
manojlds

Try this ( also corrected check for upper case and lower case, it had a bug since you grouped them as [a-zA-Z] it only looks for atleast one lower or upper. So separated them out ):

(?!^[0-9]*$)(?!^[a-z]*$)(?!^[A-Z]*$)^(.{8,15})$

Update: I found that the regex doesn't really work as expected and this is not how it is supposed to be written too!

Try something like this:

(?=^.{8,15}$)(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?!.*\s).*$

(Between 8 and 15 inclusive, contains atleast one digit, atleast one upper case and atleast one lower case and no whitespace.)

And I think this is easier to understand as well.


K
Ken White

Long, and could maybe be shortened. Supports special characters ?"-_.

\A(?=[-\?\"_a-zA-Z0-9]*?[A-Z])(?=[-\?\"_a-zA-Z0-9]*?[a-z])(?=[-\?\"_a-zA-Z0-9]*?[0-9])[-\?\"_a-zA-Z0-9]{8,15}\z

The first hyphen in [\?\"-_a-zA-Z0-9] will throw an error. You want [-\?\"_a-zA-Z0-9].
@Justin: It works fine in RegexBuddy, where I tested it before posting. Where do you get an error?
It won't necessarily throw an error in some engines, but it's invalid regex code. When you put a hyphen in a character class between two characters that don't have a valid sequence between them, it leads to undefined behavior. Some will interpret it as a literal hyphen, some will ignore it, some will throw an error, and some will use some unknown sequence scheme. Either way, when you want it interpreted as a literal hyphen, it needs to be either escaped or placed at the very beginning or end of the character class: [-foo] or [foo-] (or [^-foo] or [^foo-]).
@Justin: Thanks for the info. That's one I hadn't encountered before. Obviously I haven't had a need to use a literal hyphen. Updated my answer accordingly.
M
Max

Thanks Nicholas Carey. I was going to use regex first but what you wrote changed my mind. It is so much easier to maintain this way.

//You can set these from your custom service methods
int minLen = 8;
int minDigit 2;
int minSpChar 2;

Boolean ErrorFlag = false;
//Check for password length
if (model.NewPassword.Length < minLen)
{
    ErrorFlag = true;
    ModelState.AddModelError("NewPassword", "Password must be at least " + minLen + " characters long.");
}

//Check for Digits and Special Characters
int digitCount = 0;
int splCharCount = 0;
foreach (char c in model.NewPassword)
{
    if (char.IsDigit(c)) digitCount++;
    if (Regex.IsMatch(c.ToString(), @"[!#$%&'()*+,-.:;<=>?@[\\\]{}^_`|~]")) splCharCount++;
}

if (digitCount < minDigit)
{
    ErrorFlag = true;
    ModelState.AddModelError("NewPassword", "Password must have at least " + minDigit + " digit(s).");
}
if (splCharCount < minSpChar)
{
    ErrorFlag = true;
    ModelState.AddModelError("NewPassword", "Password must have at least " + minSpChar + " special character(s).");
}

if (ErrorFlag)
    return View(model);

M
MayankGaur

Pattern satisfy, these below criteria

Password Length 8 and Maximum 15 character Require Unique Chars Require Digit Require Lower Case Require Upper Case

^(?!.*([A-Za-z0-9]))(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,15}$

This doesn't fit the requirements in the question. The (?!.*([A-Za-z0-9])\1{1}) fragment blocks any consecutive repeated character, which isn't mentioned in your criteria and actually reduces the security of the password. You also aren't enforcing the 15-character maximum length. One less important detail: The {1} is redundant, as are the ? in all your .*? quantifiers. .*? can have a different meaning than .*, but not in this context.
A
Ashwin H A
^^(?=.*[A-Z]{"+minUpperCase+",})(?=.*[a-z]{"+minLowerCase+",})(?=.*[0-9]{"+minNumerics+",})(?=.*[!@#$\-_?.:{]{"+minSpecialChars+",})[a-zA-Z0-9!@#$\-_?.:{]{"+minLength+","+maxLength+"}$

D
Dharman

I have modified @Anurag's answer in other question to meet my requirement:

Needed the method to return a list showing where password failed to meet my policy, so first created an Enum with all possible issues:

public enum PasswordPolicyIssue
{
        MustHaveNumber,
        MustHaveCapitalLetter,
        MustHaveSmallLetter,
        MustBeAtLeast8Characters,
        MustHaveSymbol
}

Then the validation method, that returns bool and has an output List:

public bool MeetsPasswordPolicy(string password, out List<PasswordPolicyIssue> issues)
{
        var input = password;
        issues = new List<PasswordPolicyIssue>();

        if (string.IsNullOrWhiteSpace(input))
            throw new Exception("Password should not be empty");

        var hasNumber = new Regex(@"[0-9]+");
        var hasUpperChar = new Regex(@"[A-Z]+");
        //var hasMiniMaxChars = new Regex(@".{8,15}");
        var hasMinChars = new Regex(@".{8,}");
        var hasLowerChar = new Regex(@"[a-z]+");
        var hasSymbols = new Regex(@"[!@#$%^&*()_+=\[{\]};:<>|./?,-]");

        if (!hasLowerChar.IsMatch(input))
            issues.Add(PasswordPolicyIssue.MustHaveSmallLetter);

        if (!hasUpperChar.IsMatch(input))
             issues.Add(PasswordPolicyIssue.MustHaveCapitalLetter);

        if (!hasMinChars.IsMatch(input))
             issues.Add(PasswordPolicyIssue.MustBeAtLeast8Characters);

        if (!hasNumber.IsMatch(input))
             issues.Add(PasswordPolicyIssue.MustHaveNumber);

        if (!hasSymbols.IsMatch(input))
             issues.Add(PasswordPolicyIssue.MustHaveSymbol);
        
        return issues.Count() ==0;
}

Original answer by @Anurag


Simple testing online: dotnetfiddle.net/cSOFXx