2

I want users signing up on my site to choose passwords that include:

  • Minimum of 8 characters
  • At least one uppercase
  • At least one lowercase
  • At least one digit. But these are the minimum requirements. They can include any characters such as @,#,&, etc. as long as these requirements are met. I have the following regex:

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

But it doesn't match other characters asides those in the requirements listed above. I want to be able match a password that can contain any character but still meet the conditions above. How do i do this?

ss_millionaire
  • 429
  • 2
  • 7
  • 22
  • Change the `\w`s of which your password must consist to something more (`\S`, `.`) – Bergi Jun 11 '14 at 14:06
  • 1
    Please do a search next time. This question gets asked and answered a _LOT_. e.g. [regex for password](http://stackoverflow.com/a/9611715/433790) – ridgerunner Jun 11 '14 at 14:45
  • @ridgerunner Noted. But i did do a research and wrote my own regex. Just needed a little help matching minimum requirements + other characters and now i understand it. – ss_millionaire Jun 11 '14 at 15:02
  • It's an old question, but as someone who frequently encounters bizarre and unhelpful password requirements, I must query forcing your users to enter a digit and mix case. [Lengthy passwords are always more secure](https://xkcd.com/936/) than short passwords containing random digits and characters. The former are easy to remember and hard for computers to crack; the latter are impossible to remember and easy for computers to crack. – Lou Dec 15 '20 at 10:11

3 Answers3

2

This will do what you want:

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

Demo on regex101

Debuggex Demo

Explanation:

Regular expression visualization

  • ^ matches the start of the string
  • (?=.*[A-Z]) requires an uppercase character
  • (?=.*[a-z]) requires a lowercase character
  • (?=.*\d) requires a digit
  • .{8,} requires minimum length of 8.

Note: Unlike anubhava's answer, this allows for whitespace characters. If you don't want whitespace, use \S as suggested by anubhava.

Edit: Per the excellent point by @ridgerunner in the comments, here is a slightly more efficient regex:

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

This version avoids the lazy .* expression, which wastes time in testing the overall regex in this context.

elixenide
  • 44,308
  • 16
  • 74
  • 100
  • Note that you can make this regex more efficient by changing the lazy dot stars to more precise greedy expressions: `/^(?=[^A-Z]*[A-Z])(?=[^a-z]*[a-z])(?=\D*\d).{8,}$/` – ridgerunner Jun 11 '14 at 15:35
  • @ridgerunner Please can you explain to me how this regex you suggested is more efficient? – ss_millionaire Jun 11 '14 at 20:21
  • @ss_millionaire - Well, I could, but not within the limited confines of a comment here. The short answer is that the greedy expression requires fewer steps for the regex engine to declare a match. (Lazy quantifiers require one backtrack per character - a greedy one can consume them all in a single step.) I can, however, strongly recommend the following book: [Mastering Regular Expressions (3rd Edition)](http://www.amazon.com/Mastering-Regular-Expressions-Jeffrey-Friedl/dp/0596528124 "By Jeffrey Friedl. Best book on Regex - ever!") where regex efficiency is covered in great detail. – ridgerunner Jun 11 '14 at 21:35
  • @ss_millionaire As ridgerunner says, it's complicated. The short version is that `.` matches everything other than `\r` and `\n` by default. So, in matching `(?=.*[A-Z])` against `foo Bar`, the regex engine would gobble up the entire string in trying to match `.*`, then back up until it got to a character in `[A-Z]` (`B` in this case). With `(?=[^A-Z]*[A-Z])`, on the other hand, `B` doesn't match `[^A-Z]`. So, the engine just goes forward through `foo `, then matches `B`. This saves the extra steps of walking forward and backtracking. – elixenide Jun 11 '14 at 21:47
2

Don't do it all in one regex. It is far more readable and maintainable to make each validation its own check.

$passes =
    preg_match( '/[A-Z]/', $pw ) && # uppercase char
    preg_match( '/[a-z]/', $pw ) && # lowercase char
    preg_match( '/\d/', $pw ) &&    # digit
    (strlen($pw) > 8);              # at least 8 characters
Andy Lester
  • 91,102
  • 13
  • 100
  • 152
0

Use this regex:

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

I have used \S (any non space character) instead of \w (word character that means [a-zA-Z0-9_])

anubhava
  • 761,203
  • 64
  • 569
  • 643