How to Measure the Strength of a Password

How do I measure the strength of a password?

This has grown to my general brain dump of best practices for working with passwords in PHP/MySQL.
The ideas presented here are generally not my own, but the best of what I've found to date.


Ensure you are using SSL for all operations involving user information. All pages that involve these forms should check they are being called via HTTPS, and refuse to work otherwise.

You can eliminate most attacks by simply limiting the number of failed logins allowed.

Allow for relatively weak passwords, but store the number of failed logins per user and require a captcha or password verification by email if you exceed it. I set my max failures to 5.

Presenting login failures to the user needs to be carefully thought out as to not provide information to attackers.
A failed login due to a non existent user should return the same message as a failed login due to a bad password. Providing a different message will allow attackers to determine valid user logins.

Also make sure you return exactly the same message in the event of a failure for too many logins with a valid password, and a failure with too many logins and a bad password. Providing a different message will allow attackers to determine valid user passwords. A fair number of users when forced to reset their password will simply put it back to what it was.

Unfortunately limiting the number of logins allowed per IP address is not practical. Several providers such as AOL and most companies proxy their web requests. Imposing this limit will effectively eliminate these users.


I've found checking for dictionary words before submit to be inefficient as either you have to send a dictionary to the client in javascript, or send an ajax request per field change. I did this for a while and it worked ok, but didn't like the traffic it generated.

Checking for inherently weak passwords minus dictionary words IS practical client side with some simple javascript.

After submit, I check for dictionary words, and username containing password and vice versa server side. Very good dictionaries are readily downloadable and the testing against them is simple. One gotcha here is that to test for a dictionary word, you need to send a query against the database, which again contains the password. The way I got around this was to encrypt my dictionary before hand with a simple encryption and end positioned SALT and then test for the encrypted password. Not ideal, but better than plain text and only on the wire for people on your physical machines and subnet.

Once you are happy with the password they have picked encrypt it with PHP first, then store. The following password encryption function is not my idea either, but solves a number of problems. Encrypting within PHP prevents people on a shared server from intercepting your unencrypted passwords. Adding something per user that won't change (I use email as this is the username for my sites) and add a hash (SALT is a short constant string I change per site) increases resistance to attacks. Because the SALT is located within the password, and the password can be any length, it becomes almost impossible to attack this with a rainbow table.
Alternately it also means that people can't change their email and you can't change the SALT without invalidating everyone's password though.

EDIT: I would now recommend using PhPass instead of my roll your own function here, or just forget user logins altogether and use OpenID instead.

function password_crypt($email,$toHash) {
$password = str_split($toHash,(strlen($toHash)/2)+1);
return hash('sha256', $email.$password[0].SALT.$password[1]);
}

My Jqueryish client side password meter. Target should be a div. It's width will change between 0 and 100 and background color will change based on the classes denoted in the script. Again mostly stolen from other things I've found:

$.updatePasswordMeter = function(password,username,target) {
$.updatePasswordMeter._checkRepetition = function(pLen,str) {
res = ""
for ( i=0; i<str.length ; i++ ) {
repeated=true;
for (j=0;j < pLen && (j+i+pLen) < str.length;j++)
repeated=repeated && (str.charAt(j+i)==str.charAt(j+i+pLen));
if (j<pLen) repeated=false;
if (repeated) {
i+=pLen-1;
repeated=false;
}
else {
res+=str.charAt(i);
};
};
return res;
};
var score = 0;
var r_class = 'weak-password';
//password < 4
if (password.length < 4 || password.toLowerCase()==username.toLowerCase()) {
target.width(score + '%').removeClass("weak-password okay-password good-password strong-password"
).addClass(r_class);
return true;
}
//password length
score += password.length * 4;
score += ( $.updatePasswordMeter._checkRepetition(1,password).length - password.length ) * 1;
score += ( $.updatePasswordMeter._checkRepetition(2,password).length - password.length ) * 1;
score += ( $.updatePasswordMeter._checkRepetition(3,password).length - password.length ) * 1;
score += ( $.updatePasswordMeter._checkRepetition(4,password).length - password.length ) * 1;
//password has 3 numbers
if (password.match(/(.*[0-9].*[0-9].*[0-9])/)) score += 5;

//password has 2 symbols
if (password.match(/(.*[!,@,#,$,%,^,&,*,?,_,~].*[!,@,#,$,%,^,&,*,?,_,~])/)) score += 5;

//password has Upper and Lower chars
if (password.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/)) score += 10;

//password has number and chars
if (password.match(/([a-zA-Z])/) && password.match(/([0-9])/)) score += 15;
//
//password has number and symbol
if (password.match(/([!,@,#,$,%,^,&,*,?,_,~])/) && password.match(/([0-9])/)) score += 15;

//password has char and symbol
if (password.match(/([!,@,#,$,%,^,&,*,?,_,~])/) && password.match(/([a-zA-Z])/)) score += 15;

//password is just a nubers or chars
if (password.match(/^\w+$/) || password.match(/^\d+$/) ) score -= 10;

//verifing 0 < score < 100
score = score * 2;
if ( score < 0 ) score = 0;
if ( score > 100 ) score = 100;
if (score > 25 ) r_class = 'okay-password';
if (score > 50 ) r_class = 'good-password';
if (score > 75 ) r_class = 'strong-password';
target.width(score + '%').removeClass("weak-password okay-password good-password strong-password"
).addClass(r_class);
return true;
};

How to calculate password strength?

It's all a question of entropy. How many different symbols are there to test ?

Traditionally, passwords are a string of characters. Symbols are then characters. If you use lower case letters only a-z is a range of 26 possible letters. With upper case and numbers, you get 62 symbols. With all special symbols that are in the ASCII set (so without fancy encodings) you get over 90 possible symbols already. In your case, a symbol is a word.

From this question on Oxford dictionaries’ website I would gather there are 115000 words that you could expect (without obsolete and derivatives).

To compute the number of combinations, you have to realize that for each possible symbol at a given position, you have the choice of every possible character at another position. With strings of characters, if your password starts with a $, you still have any character for the other positions. This means that we have to multiply the number of possible symbols for each symbol position. Thus with 2 characters that have s possible symbols, you have s*s possibilities. In general, you would have for c characters sc possibilities for a password.

Note that this means that in the case of dictionary words, you put random words instead of making sentences !

In your case, there are 11500012 possibilities, which is about 5.3*1060. So a huge lot.

The time to brute-force a password is then given by how much time t it takes to test a password, and the number of attempts, in your case t × 2.65 × 10^60 if you enumerate all combinations in a random order, and t × 5.3 × 10^60 if you try word combinations completely at random.

What is the best way to check the strength of a password?

1: Eliminate often used passwords
Check the entered passwords against a list of often used passwords (see e.g. the top 100.000 passwords in the leaked LinkedIn password list: http://www.adeptus-mechanicus.com/codex/linkhap/combo_not.zip), make sure to include leetspeek substitutions:
A@, E3, B8, S5, etc.

Remove parts of the password that hit against this list from the entered phrase, before going to part 2 below.

2: Don't force any rules on the user

The golden rule of passwords is that longer is better.

Forget about forced use of caps, numbers, and symbols because (the vast majority of) users will:
- Make the first letter a capital;
- Put the number 1 at the end;
- Put a ! after that if a symbol is required.

Instead check password strength

For a decent starting point see: http://www.passwordmeter.com/

I suggest as a minimum the following rules:

Additions (better passwords)
-----------------------------
- Number of Characters Flat +(n*4)
- Uppercase Letters Cond/Incr +((len-n)*2)
- Lowercase Letters Cond/Incr +((len-n)*2)
- Numbers Cond +(n*4)
- Symbols Flat +(n*6)
- Middle Numbers or Symbols Flat +(n*2)
- Shannon Entropy Complex *EntropyScore

Deductions (worse passwords)
-----------------------------
- Letters Only Flat -n
- Numbers Only Flat -(n*16)
- Repeat Chars (Case Insensitive) Complex -
- Consecutive Uppercase Letters Flat -(n*2)
- Consecutive Lowercase Letters Flat -(n*2)
- Consecutive Numbers Flat -(n*2)
- Sequential Letters (3+) Flat -(n*3)
- Sequential Numbers (3+) Flat -(n*3)
- Sequential Symbols (3+) Flat -(n*3)
- Repeated words Complex -
- Only 1st char is uppercase Flat -n
- Last (non symbol) char is number Flat -n
- Only last char is symbol Flat -n

Just following passwordmeter is not enough, because sure enough its naive algorithm sees Password1! as good, whereas it is exceptionally weak.
Make sure to disregard initial capital letters when scoring as well as trailing numbers and symbols (as per the last 3 rules).

Calculating Shannon entropy
See: Fastest way to compute entropy in Python

3: Don't allow any passwords that are too weak
Rather than forcing the user to bend to self-defeating rules, allow anything that will give a high enough score. How high depends on your use case.

And most importantly
When you accept the password and store it in a database, make sure to salt and hash it!.

How to calculate password complexity

Using something like cracklib is very good if you can afford the time of checking against all of the potential rules. If you just want something quick -- say for a javascript-based strength meter -- then consider estimating the number of potential guesses that would be required for a brute force attack. For every character type seen update a multiplier based on the number of potential characters of that type. So if you have only digits, then the multiplier would be 10. If you have only lowercase, then the multiplier is 26. If both, then the multiplier is 36 -- that is for each character in the password, a brute force attack would need to try up to 36 different characters. A password containing both upper and lowercase characters, digits, and punctuation, then would have a multiplier of 10 + 26 + 26 + 32 = 94 (more or less depending on the allowable punctuation).

To estimate the maximum number of permutations a brute force method would take, raise the multiplier to the power equal to the number of digits in the password. This gives you then maximum number of guesses it would take to break the password using a brute force attack. Assume that each guess takes one cpu cycle and given the fastest processor calculate how long it would take to break a password given a certain number of permutations. For example, if my multiplier was 10 and the password was 10 characters long, then I would have 10,000,000,000 potential combinations. On 3GHz processor, this ought to take 10/3 * k or 3k seconds (where k is the number of cycles per guess, typically small). Clearly, this is a weak password.

Now, establish some ranges that represent reasonable password strengths. For example, if you think that an 8 character password with upper and lowercase characters is minimally required for medium strength, then your cutoff would be 52^8 or approximately 1.5 years on a 3GHz processor (assuming k = 1). If you add in digits, then the cutoff becomes 62^8 or approximately 8 years on 3GHz processor.

To put it in use, then you only need keep track of which kinds of characters you see, construct the appropriate multiplier, calculate the expected permutations based on password length, and compare it against your predefined cutoffs to determine what strength the password has.

Standardizing of password strength algorithm

Standardizing the strength of passwords using relative terms like "weak" and "strong" is kind of like standardizing the strength of a lock using similar terms. You can't measure "weak" or "strong"; what you can measure is how long it would take to break (similar to security ratings on physical locks/safes), acceptable tolerances of candidate keys (how close does a fingerprint, or key grinding, have to be to the original sample the lock was keyed to in order to work), etc.

Fredley's algorithm will give you a number. That number can be compared to any other, and much like any measurement you can quantify relative strength or weakness. As for defining standards like "X is weak", "Y is normal", I don't think you'll get all interested parties to agree, because the absolute strength of a key must always be taken in reference to the value of the resource it protects. You (or your daughter) wouldn't put her diary in a safe deposit box at the bank behind a vault door; an 80-cent toy lock would provide "strong" security against those she didn't want reading it (snot-nosed little or big brother, sleepover guests, etc). However, that 80-cent toy lock wouldn't last two seconds on the door of your safe deposit box, where the deed to your house, the title to your car, and your great-grandmother's one-off 25-carat diamond necklace are kept.

Similarly, the password (or more accurately the compound security measure) required for a bank website will be stronger than for an online forum because the resource it protects has more material value. The forum's just words (though there are some famous cases of Facebook hacking resulting in compromising photo leaks); if a hacker gets into your bank records, they can seriously ruin your life.

So, "MittensABC" would be an acceptable password for most forums because it's longer than 8 characters (26^8 = roughly 208 billion combinations of random letters) and contains uppercase and lowercase letters (the sample space required to "brute-force" this password would be 52^10 = 144 quadrillion possible combinations of 10-character case-sensitive strings). However, it would be totally inadequate for a bank: it is made up primarily of a dictionary word, and is different only because you added an easy-to-guess letter combination, so an "intelligent" cracking alg could reduce the initial sample space to the 100k or so words in modern usage, and append basic letter and number strings to come up with maybe a couple million possibilities (child's play for a computer to churn through). By the same token, many sites that offer access to very private information don't allow use of birthdays, SSNs, etc. in passwords, since if that information were stolen it could be given as hints to a cracking algorithm, further reducing the initial likely sample space.

In summary, what makes a "good" password depends on how likely a hacker/cracker will be to persist at trying to break that password. There are other useful tools like a 10-second cooldown between login attempts, or a certain number of attempts in a 15-minute period, which make it virtually impossible to leverage the speed of a computer, but an intelligent algorithm, a website vulnerability, or a dumb user can all increase the chances of a successful hack.

Test the Strength of a Password in Python

There are a number of problems with your code; Notably, you are replacing the lowercase chars with x, uppercase with y and digits with n, before calling is_english_word - that means is_english_word() will be called with 'Xyyyyyyyyyyyyyy' which is not an english word. That is making your password not 'WEAK'.

Since it is also not 'STRONG', it ends up being 'MEDIUM'.

For the record, here is an example of a correct code to do what you want:

import string
def password_strength(string):
if len(string) < 8 or is_english_word(string):
return 'WEAK'
elif (len(string) > 11 and
any(ch in string.ascii_lowercase for ch in string) and
any(ch in string.ascii_uppercase for ch in string) and
any(ch.isdigit() for ch in string)):
return 'STRONG'
else:
return 'MEDIUM'

How to check password strength?

Basic but a logical one:

enum PasswordScore
{
Blank = 0,
VeryWeak = 1,
Weak = 2,
Medium = 3,
Strong = 4,
VeryStrong = 5
}

public class PasswordAdvisor
{
public static PasswordScore CheckStrength(string password)
{
int score = 1;

if (password.Length < 1)
return PasswordScore.Blank;
if (password.Length < 4)
return PasswordScore.VeryWeak;

if (password.Length >= 8)
score++;
if (password.Length >= 12)
score++;
if (Regex.Match(password, @"/\d+/", RegexOptions.ECMAScript))
score++;
if (Regex.Match(password, @"/[a-z]/", RegexOptions.ECMAScript) &&
Regex.Match(password, @"/[A-Z]/", RegexOptions.ECMAScript))
score++;
if (Regex.Match(password, @"/.[!,@,#,$,%,^,&,*,?,_,~,-,£,(,)]/", RegexOptions.ECMAScript))
score++;

return (PasswordScore)score;
}
}

Ref: http://passwordadvisor.com/CodeAspNet.aspx

Guidelines for a Password Strength Meter (Password checker) Design

Consider the following:

  • Length
  • Mixed case
  • Not many repeated characters
  • Includes letters, numbers, and symbols
  • Does not include part of the username
  • Not similar to prior passwords
  • Does not hash to the same thing as a weak password
  • Is not a keyboard walk
  • Is not related to the individual
  • Does not end with the common suffixes
  • Does not start with the common prefixes

See Bruce Schneier's post on passwords as well as this post.



Related Topics



Leave a reply



Submit