6

I need a regex that tests a string for a

  • minimum of 14 characters - valid are A-Za-z0-9#,.-_
  • minimum of 6 letters within that 14
  • minimum of 2 numbers within that 14

Is there a way I can wrap this up in one regular expression (currently I have a javascript and php function that does three separate tests, one that it is 14 total, another that there is at least two numbers, and another that there is at least 6 letters.

So the following would be valid:

  • blabla2bla2f54a (valid >14 total, with at least 6 letters, at least 2 numbers)
  • thisIsNotValidAtAll (invalid because less than 2 numbers)
deceze
  • 510,633
  • 85
  • 743
  • 889
Chris
  • 54,599
  • 30
  • 149
  • 186
  • 1
    Is this possible in Javascript? My gut is thinking that this is possible in Perl. Look ahead syntax for regular expression perhaps??? – Yzmir Ramirez Apr 03 '11 at 02:42
  • 1
    Keep your working solution. Compacting this into one regex is hardly doable without expliciting the required content as permutations. Assertions in PCRE fall flat because they must be fixed length. – mario Apr 03 '11 at 02:42
  • Hmmmm, interesting. I'm reasonably comfortable with regular expressions but I just thought I was missing something - that there was a solution that I simply wasn't seeing. Looks like there mightn't be, or not a viable one – Chris Apr 03 '11 at 02:45
  • This is easily done in a single JavaScript regex. See my answer below... – ridgerunner Apr 03 '11 at 03:43
  • possible duplicate of [PHP regular expression: string must contain to types of characters](http://stackoverflow.com/questions/5252008/php-regular-expression-string-must-contain-to-types-of-characters) – ircmaxell Apr 14 '12 at 15:51

4 Answers4

17

Easy! First lets look at a commented version in PHP:

$re = '/# Match 14+ char password with min 2 digits and 6 letters.
    ^                       # Anchor to start of string.
    (?=(?:.*?[A-Za-z]){6})  # minimum of 6 letters.
    (?=(?:.*?[0-9]){2})     # minimum of 2 numbers.
    [A-Za-z0-9#,.\-_]{14,}  # Match minimum of 14 characters.
    $                       # Anchor to end of string.
    /x';

Here is the JavaScript version:

var re = /^(?=(?:.*?[A-Za-z]){6})(?=(?:.*?[0-9]){2})[A-Za-z0-9#,.\-_]{14,}$/;

Addendum 2012-11-30

I noticed that this answer recently got an upvote. This uses a more outdated expression so I figured it was time to update it with a better one.

=== A more efficient expression ===

By getting rid of the "dot-star" altogether and greedily applying a more precise expression, (a negated char class), an even more efficient solution results:

$re = '/# Match 14+ char password with min 2 digits and 6 letters.
    ^                              # Anchor to start of string.
    (?=(?:[^A-Za-z]*[A-Za-z]){6})  # minimum of 6 letters.
    (?=(?:[^0-9]*[0-9]){2})        # minimum of 2 numbers.
    [A-Za-z0-9#,.\-_]{14,}         # Match minimum of 14 characters.
    $                              # Anchor to end of string.
    /x';

Here is the new JavaScript version:

var re = /^(?=(?:[^A-Za-z]*[A-Za-z]){6})(?=(?:[^0-9]*[0-9]){2})[A-Za-z0-9#,.\-_]{14,}$/;

  • Edit 1: Added #,.-_ to list of valid chars.
  • Edit 2: Changed the greedy to lazy star.
  • Edit 2012-11-30: Added alternate version with the "lazy-dot-star" replaced with a more efficient greedy application of a more precise expression.
informatik01
  • 16,038
  • 10
  • 74
  • 104
ridgerunner
  • 33,777
  • 5
  • 57
  • 69
  • @ridgerunner I like it, and I pretty much understand it. I;m going to have to do a bit more reading on positive look ahead's I think. One question though, why is a minimum of 2/6 done as {2}/{6}, not {2,}/{6,}? – Chris Apr 03 '11 at 03:52
  • 2
    @Cris: Since each requirement is only a minimum, once the minimum number is matched, there is no need to match any more (so why bother - just extra unnecessary work). – ridgerunner Apr 03 '11 at 03:58
  • @ridgerunner - just one more question, to include `#,.-_` as per the question, I would just need to include them in the final clause `[A-Za-z0-9#,\.-_]{14,0}`, correct? – Chris Apr 03 '11 at 03:59
  • @ridgerunner, is it possible to match `#,.-_` 0 or more times :)? – Dejan Marjanović Apr 03 '11 at 04:01
  • Almost, you need to escape the `-`character because it is used to specify ranges. so `[A-Za-z0-9#,.\-_]{14,}` – david Apr 03 '11 at 04:03
  • David is correct. The - must be escaped in a char class (unless you place it at the very start or end). Personally, I like to escape it. Have updated the answer. – ridgerunner Apr 03 '11 at 04:13
  • @david @ridgerunner, you are right, if `-` is not escaped then `?` can go through. – Dejan Marjanović Apr 03 '11 at 04:15
  • @ridgerunner, @david, @webarto Thanks for discussing it and fleshing out the problem at hand. I am very happy with ridgerunners answer, and will mark his as correct. Thanks ridge – Chris Apr 03 '11 at 04:16
  • 1
    @ridgerunner I see you removed the lazy star, you should probably put it back in as it saves a lot of backtracking and speeds things up quite a bit. Here is a testcase showing the difference: http://jsperf.com/regexquestionmarktest – david Apr 03 '11 at 05:17
  • @david: You're right. I let a previous version slip back in on one of my edits. Thanks for the eagle eye! – ridgerunner Apr 03 '11 at 05:51
5

I would recommend multiple checks, writing a single regex for this would be ugly. Multiple checks also allows you to know what criteria wasn't met.

$input = 'blabla2bla2f54a';
$errors=array();
if (!preg_match('/^[A-Za-z0-9#,.\-_]*$/', $input))
    $errors[] = 'Invalid characters';
if (strlen($input) < 14)
    $errors[] = 'Not long enough';
if (strlen(preg_replace('/[^0-9]/','',$input)) < 2)
    $errors[] = 'Not enough numbers';
if (strlen(preg_replace('/[^A-Za-z]/','',$input)) < 6)
    $errors[] = 'Not enough letters';

if (count($errors) > 0) //Didn't work
{
    echo implode($errors,'<BR/>');
}
Jess
  • 8,628
  • 6
  • 49
  • 67
2
echo preg_match("/(?=.*[#,.-_])((?=.*\d{2,})(?=.*[a-zA-Z]{6,}).{14,})/", $string);

Output:

blabla2bla2f54a (1)
thisIsNotValidAtAll (0)
Dejan Marjanović
  • 19,244
  • 7
  • 52
  • 66
  • Should those be {2,}, {6,}, and {14,} since its "or more"? This is using the If-Then-Else syntax, right? http://www.regular-expressions.info/conditional.html – Yzmir Ramirez Apr 03 '11 at 02:47
  • @webarto seems very close, but doesn't work for `aaa2aaaaaaaaaaaaa2aaa` but does work for `aaa2aaaaaaaaaaaaa22aaa`. It seems the positive look ahead requires that there is 2 in sequence? – Chris Apr 03 '11 at 02:52
  • Give me a minute, I'll figure it out :) – Dejan Marjanović Apr 03 '11 at 02:57
  • @Chris, yes it looks in sequence, I can't get it work non sequential if it's possible at all. – Dejan Marjanović Apr 03 '11 at 03:47
  • @webarto Thanks for looking into it, it looks like you were really close! It appears ridgerunner has it pretty much spot on. – Chris Apr 03 '11 at 03:56
-1

No, that's not possible, because in fact you want to do three individual checks. You can't wrap them up in one expression that returns true or false. The problem is, that you have two equal checks that you would need to combine via an AND-operator, whicht is not possible. But what we can do is building a super-size RegExp that recognizes every possible case. But hat kind of RegExp is senseless, because it would take a long time to apply this test on your string. I would recommend you doing three separated tests:

var result = string.replace(/[A-Za-z]{6}/, "").replace(/[0-9]{2}/, "").replace(/[A-Za-z0-9#,\.\-]{6}/, "").length == 0 ? true : false; // By shortening our sting we're saving time on later chained replacement methods
buschtoens
  • 8,091
  • 9
  • 42
  • 60