1

I have written a regular expression to validate password on the basis of below conditions.

Your password length should be 8 to 20 and must contain at least 2 letters, 1 number and 1 special character. Space not allowed

My regex is-

final String PASSWORD_PATTERN = 
   "^(?=.*[0-9].{1})(?=.*[A-Za-z].{2})(?=.*[@#$%^&+=].{1})(?=\\S+$).{8,}$";

It is working in all other cases except if I am entering any special character at the end of the string. Please anyone suggest what is wrong I am doing?

PA.
  • 28,486
  • 9
  • 71
  • 95
Vid
  • 1,012
  • 1
  • 14
  • 29
  • did you use `trim()` (For string) when matching ? – IntelliJ Amiya Jan 22 '16 at 08:30
  • Yes, even though it is not accepting. – Vid Jan 22 '16 at 08:35
  • 1
    The pattern does not meet your specifications. – Wiktor Stribiżew Jan 22 '16 at 08:36
  • problem is your `PASSWORD_PATTERN` – IntelliJ Amiya Jan 22 '16 at 08:36
  • Yes, this is what the problem is, however I need the solution for this. Where I am doing wrong in the pattern? – Vid Jan 22 '16 at 08:37
  • Quantifiers are applied to `.` and not to the right pattern. – Wiktor Stribiżew Jan 22 '16 at 08:40
  • If one **absolutely** doesn't like regular expressions, one can write a function to iterate over the string and count the number of digits, letters or special characters. `int a=0, d=0, s=0; if (pass.length() < 8 || pass.length() > 20) return false; for (int i = 0; i < pass.length(); i++) { char ch = (pass.charAt(i); if (Character.isLetter(ch)) a++; else if (Character.isDigit(ch)) d++; else if (CharacterUtils.isSpecialChar(ch)) /* Need to write your own method to check if char is special */ s++; else if (Character.isWhiteSpace(ch)) return false; } return !(a < 2 || d < 1 || s < 1);` – MC Emperor Jan 22 '16 at 09:01

2 Answers2

2

If one does not like using regular expressions, one can write a function to iterate over the string and count the number of digits, letters or special characters.

boolean isValidPassword(char[] pass) {
    int letters = 0, digits = 0, specialChars = 0;
    if (pass.length < 8 || pass.length > 20) {
        return false;
    }
    for (int i = 0; i < pass.length; i++) {
        char ch = pass[i];
        if (Character.isLetter(ch)) {
            letters++;
        }
        else if (Character.isDigit(ch)) {
            digits++;
        }
        else if (isSpecialChar(ch)) {
            specialChars++;
        }
        else if (Character.isWhiteSpace(ch)) {
            return false;
        }
    }
    return !(letters < 2 || digits < 1 || specialChars < 1);
}

static boolean isSpecialChar(char c) {
    switch (c) {
        case '@':
        case '#':
        case '$':
        case '%':
        case '^':
        case '&':
        case '+':
        case '=':
            return true;
        default:
            return false;
    }
}

Note that this also tackles the security problem when using Strings, as described by this article and this question.

Community
  • 1
  • 1
MC Emperor
  • 22,334
  • 15
  • 80
  • 130
0

The pattern does not meet your specifications, because the quantifiers {1}, {2} are applied to . and not to the right pattern. Also, the last quantifier {8,} allows any number of characters from 8, but you need to cap their number to 20. Also, note that {1} is totally redundant. Besides, I suggest using the principle of contrast inside the lookaheads (check Lookahead Example: Simple Password Validation).

You can use

final String PASSWORD_PATTERN = 
  "^(?=[^0-9]*[0-9])(?=(?:[^A-Za-z]*[A-Za-z]){2})(?=[^@#$%^&+=]*[@#$%^&+=])\\S{8,20}$";

See regex demo

Explanation:

  • ^ - string start
  • (?=[^0-9]*[0-9]) - there must be at least one digit (after optional non-digits)
  • (?=(?:[^A-Za-z]*[A-Za-z]){2}) - there should be 2 ASCII letters (use \pL to match Unicode letters and its counterclass \PL to match characters other than Unicode letters)
  • (?=[^@#$%^&+=]*[@#$%^&+=]) - there must be at least 1 special symbol from the set
  • \\S{8,20} - 8 to 20 non-whitespace symbols
  • $ - end of string

If any of the lookaheads return false, the whole match is failed. All of them are executed only once, right after the string start, before the first symbol.

Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
  • 1
    Nice, you made this regular expression *uncomplicated*. – MC Emperor Jan 22 '16 at 08:50
  • 1
    Yes, it is working fine. However, will not work if I'll enter any special char like (!, -, ", ;..). So, do we have any set in regex (like for digit it's d similarly for special char) which contain all this special char? – Vid Jan 22 '16 at 08:57
  • Use [`^(?=[^0-9]*[0-9])(?=(?:[^A-Za-z]*[A-Za-z]){2})(?=[^\\pP\\pS]*[\\pP\\pS])\\S{8,20}$`](https://regex101.com/r/cJ3pL0/2). `\pP` matches punctuation and `\pS` matches symbols. – Wiktor Stribiżew Jan 22 '16 at 08:59
  • java.util.regex.PatternSyntaxException: Incorrect Unicode property near index 54: 01-22 14:56:48.483 30115-30115/com.visualoan E/AndroidRuntime: ^(?=[^0-9]*[0-9])(?=(?:[^A-Za-z]*[A-Za-z]){2})(?=[^\pP\pS]*[\pP\pS])\S{8,20}$‌​ – Vid Jan 22 '16 at 09:29
  • It is Android, it uses a bit different regex than Java. Try using `\\p{P}` or `\\p{IsP}` instead of `\\pP` (similarly for `\\pS`). – Wiktor Stribiżew Jan 22 '16 at 09:36
  • Do those work? If not, try using POSIX classes, too: `\\p{Punct}` and its counter class `\\P{Punct}`. Or `[\\p{Pd}\\p{Ps}\\p{Pe}\\p{Pc}\\p{Pi}\\p{Pf}\\p{Po}]` to match punctuation and `[\\p{Sm}\\p{Sc}\\p{Sk}\\p{So}]` to match all symbols. Add `^` after `[` to make negated character classes. – Wiktor Stribiżew Jan 22 '16 at 10:17