About two years ago, I wrote a little function to calculate the complexity of a password based on the usage of character classes. My goal was to not force people into adding '123!' to their password just to use all character classes, but instead allow pure character passwords with an equivalent complexity. That way the user can choose if wants to use an 8 character password with upper case, lower case and numbers or a much longer password with lower case letters only (or something in between).
The function is far from perfect as it was just the first implementation of that idea and never run in production. But I think as a base for a discussion it should be good enough.
function passwordComplexEnough($password){
$chars=0;
if(preg_match('/\p{Lu}+/', $password) === 1){ // letter upper case
$chars+=26;
}
if(preg_match('/\p{Ll}+/', $password) === 1){ // letter lower case
$chars+=26;
}
if(preg_match('/\p{Nd}+/', $password) === 1){ // decimal digit number
$chars+=10;
}
if(preg_match('/\p{S}+/', $password) === 1){ // symbol
// seems pessimistic but reasonable
$chars+=10;
}
$len = mb_strlen($password);
$complexity = pow($chars, $len);
// 62^8 (62 chars and length 8 as minimum complexity)
return ($complexity >= 218340105584896);
}
I think it might be useful as a hint but if you're trying to enforce good passwords it falls short like every other scheme. For instance if I'm not mistaken "PasswordPassword" would be accepted by your function.
I always think there are two ways to look at the password issue:
- It's the user's responsibility and you live it up to them not to use something stupidly weak if they value their accounts (at best you can hint them that the password is weak, but if they want to use "qwerty1234" you let them). This is the approach I expect from websites like HN, reddit, and the like.
- You want to protect the user and yourself, maybe because you're a website handling money transactions or you're a corporate website and you want to make sure every user has a decent password to access the company's resources etc... In this case the only satisfactory solution is to enforce strong 2FA and if that's really not an option then generate the password for the user. Otherwise they'll always figure out a way to reuse their passwords, generate a weak passwords that passes the predicate etc...
Because in the end, while I'm sure most people would agree that "oothe*Nah2phao0t" is a very strong password, what good is it if I reuse it across a large variety of websites? What about phishing and social engineering? I'm going to go out on a limb and guess that there probably are more Facebook account hacked because of password reuse and phishing websites than people blindly guessing passwords.
Apparently one of the more common passwords are "Password2018" (or 2017, 2016 etc, depending on current year). Nice and long, mixes three character classes. Not very secure...
Seems reasonable, though there are some strings that pass that happen to be in precomputed rainbow tables, like "0xDEADBEEF" or "secretpassword". Nothing's perfect though. Passwords just suck.
The function is far from perfect as it was just the first implementation of that idea and never run in production. But I think as a base for a discussion it should be good enough.
What do you think of this approach?