3

What would be the correct regex, to satisfy the following password criteria:

  • Must include at least 1 lower-case letter.
  • Must include at least 1 upper-case letter.
  • Must include at least 1 number.
  • Must include at least 1 special character (only the following special characters are allowed: !#%).
  • Must NOT include any other characters then A-Za-z0-9!#% (must not include ; for example).
  • Must be from 8 to 32 characters long.

This is what i tried, but it doesn't work:

^(?=.*?[a-z])(?=.*?[A-Z])(?=.*?[0-9])(?=.*?[\!\#\@\$\%\&\/\(\)\=\?\*\-\+\-\_\.\:\;\,\]\[\{\}\^]).{8,32}

But it should be:

^(?=.*?[a-z])(?=.*?[A-Z])(?=.*?[0-9])(?=.*?[\!\#\@\$\%\&\/\(\)\=\?\*\-\+\-\_\.\:\;\,\]\[\{\}\^])[A-Za-z0-9!#%]{8,32}

But Unihedron's solution is better anyways, just wanted to mention this for the users which will read this question in the future. :)

Unihedron's solution (can also be found in his answer below, i copied it for myself, just in case he changes (updates it to an better version) it in his answer):

^(?=[^a-z]*[a-z])(?=[^A-Z]*[A-Z])(?=\D*\d)(?=.*?[!#%])[A-Za-z0-9!#%]{8,32}$

I ended up with the following regex:

^(?=[^a-z]*[a-z])(?=[^A-Z]*[A-Z])(?=\D*\d)(?=.*?[\!\#\@\$\%\&\/\(\)\=\?\*\-\+\-\_\.\:\;\,\]\[\{\}\^])[A-Za-z0-9\!\#\@\$\%\&\/\(\)\=\?\*\-\+\-\_\.\:\;\,\]\[\{\}\^]{8,60}$

Thanks again Unihedron and skamazin. Appreciated!

Jo Smo
  • 6,923
  • 9
  • 47
  • 67
  • share your efforts as well. – Braj Aug 20 '14 at 18:19
  • if it is only going to be 32 characters long...why dont you just walk the string 5 times...still O(1) in that case – Steve Aug 20 '14 at 18:21
  • @Steve don't know if i got you, are you suggesting 5 regexes? – Jo Smo Aug 20 '14 at 18:22
  • no..what i mean is you dont even need regex....but guess you found the answer – Steve Aug 20 '14 at 19:13
  • @Steve oh... I'm need this in PHP, is there a way to go through the string with a loop like `for()` like in C# for example? Or would i need to use a function like [str_split()](http://php.net/manual/en/function.str-split.php)? – Jo Smo Aug 20 '14 at 19:16
  • @Steve Still better to write a one-liner code with regex than going through the work to parse the string by building your own finite state engine. Robust code and better maintainability! – Unihedron Aug 20 '14 at 19:17
  • 1
    By the way, you shouldn't worry about post changes, you can always [view revisions of posts](http://stackoverflow.com/posts/25411900/revisions) when they are edited. – Unihedron Aug 20 '14 at 19:17
  • @Steve Would iterating through the string be faster then the `regex`? – Jo Smo Aug 20 '14 at 19:22
  • @Unihedron i know... But it's a few more clicks away. :) – Jo Smo Aug 20 '14 at 19:25
  • 1
    IDK about PHP, but you can do something like. ToLower() and then compare with original string, ToUpper() and compare. Replace (0-9) with '' and compare ect. Since the length is so short you wouldnt have to worry about the run time at all. IMO this improves readability comparing to regex. donno if it is faster though – Steve Aug 20 '14 at 19:53
  • @Steve i will experiment with it a bit, will post the performance results when i'm done. Just hope that i won't forget to post them here. :/ – Jo Smo Aug 20 '14 at 21:55
  • 1
    @Steve Readable code? [Nonono, don't ever...](https://www.thc.org/root/phun/unmaintain.html) – Unihedron Aug 21 '14 at 07:47
  • @Unihedron i think that the computers/servers are better these days, so i guess that until it's not extremely urgent, then for the sake of maintainability, it's better to use the `regex` method. If if i should have the desire for "crazy performance" or if it should get extremely urgent, then i Steve's approach will be appreciated (that's why i up-voted steves idea as well). Good to know that you have multiple options. Thanks again! :) – Jo Smo Aug 21 '14 at 11:01

2 Answers2

8

Use this regex:

/^(?=[^a-z]*[a-z])(?=[^A-Z]*[A-Z])(?=\D*\d)(?=[^!#%]*[!#%])[A-Za-z0-9!#%]{8,32}$/

Here is a regex demo!


Read more:

Community
  • 1
  • 1
Unihedron
  • 10,902
  • 13
  • 62
  • 72
  • @Unihedron what does the star `*` do? – Jo Smo Aug 20 '14 at 18:32
  • 1
    @tastro Stars are the `zero to one` quantifier. Using these in the regex, it allows efficient matching by __skipping ahead of irrelevant characters__ (no backtracking). Also see the [Stack Overflow Regex Reference](http://stackoverflow.com/questions/22937618/reference-what-does-this-regex-mean). – Unihedron Aug 20 '14 at 18:33
  • 1
    @Unihedron Can you clear something up for me: Using `(?=[^a-z]*[a-z])` is faster (number of steps that regex engine makes) than `(?=.*[a-z])` because the second one would make the engine backtrack until it matches, correct? The first one would require no backtracking because `[^a-z]*` would stop on the first alpha character and then `[a-z]` would match that alpha character, correct? – skamazin Aug 20 '14 at 18:36
  • 2
    @skamazin Yes, that is correct. This technique can be explained by the way regex engines work - there are two pointers, one on the pattern, the other on your String. The pointer executes as much as possible, hence optimal regexes are the ones that prevent backtracking (`.*?` is favourable over `.*` for literal sequences) or "roll"s over the unnecessary content appropriately (see [this](http://stackoverflow.com/a/25352148/3622940) for a good example - `,?[^,]*` is magical!). – Unihedron Aug 20 '14 at 18:39
2

Test your possible passwords on this and see if they give you the proper result

The regex I used is:

^(?=.*[a-z])(?=.*[A-Z])(?=.*?[0-9])(?=.*?[!#%])[A-Za-z0-9!#%]{8,32}$
skamazin
  • 757
  • 5
  • 12
  • I apologize, I read the question as 1 number OR 1 special character so I made a non-capturing alternation group. My fault. – skamazin Aug 20 '14 at 18:29
  • @skamazin which part of your `regex` makes sure that there can't be an semicolon `;` (for example) as a special character? Just want to understand the `regex` better. :) – Jo Smo Aug 20 '14 at 18:38
  • @tastro The last bit: The first four groups asserts the conditions that the characters are present. `[A-Za-z0-9!#%]{8,32}` Means the entire string is composed of 8 to 32 characters from the list `A-Z`, `a-z`, `0-9`, `!` `#` or `%`. – Unihedron Aug 20 '14 at 18:41
  • @Unihedron i know, i red your answer from your link above. But Which part assures that the password can't be `Aa!12345678;` (just an example)? Which part prevents the use of the `;` (semicolon)? – Jo Smo Aug 20 '14 at 18:45
  • 2
    @tastro The entire match is __anchored__ with `^` and `$`, hence `[...]{n,u}` within will assert that this list completely endorses the string area. [Lookaheads do not affect the match.](http://www.rexegg.com/regex-lookarounds.html) – Unihedron Aug 20 '14 at 18:47
  • 2
    As Unihedron already indicating, the lookaheads don't capture, so all the capturing is taking place at the end of the regex; specifically this bit:`[A-Za-z0-9!#%]{8,32}` Which will make only if the strings is composed of Upper/lower case alphas, numbers, and `!#%`. Because `;` is not included, it will not be allowed to match the regex. If you were to add `;` to the list, it would allow that character. `[A-Za-z0-9!#%]` is a white-list, meaning those are the only characters allowed. – skamazin Aug 20 '14 at 18:49
  • @skamazin aaaa, now everything is clear to me. I just didn't get why my regex didn't work... I didn't know that the lookaheads don't capture. Thank you for making me understand this! :) – Jo Smo Aug 20 '14 at 19:00