46

What is the best way of ensuring that a user supplied password is a strong password in a registration or change password form?

One idea I had (in python)

def validate_password(passwd):
    conditions_met = 0
    conditions_total = 3
    if len(passwd) >= 6: 
        if passwd.lower() != passwd: conditions_met += 1
        if len([x for x in passwd if x.isdigit()]) > 0: conditions_met += 1
        if len([x for x in passwd if not x.isalnum()]) > 0: conditions_met += 1
    result = False
    print conditions_met
    if conditions_met >= 2: result = True
    return result
Braiam
  • 1
  • 11
  • 47
  • 78
Ed L
  • 1,947
  • 2
  • 17
  • 30
  • 2
    A good question with very poor answers. None of the answers actually makes for good passwords. – Johan May 23 '18 at 13:00
  • 1
    @Johan Actually this is a bad question. It's asking for opinionated ways to do something where implementations and quality necessarily differ based on context, and doesn't even provide any metrics by which to measure the quality of an answer. – TylerH May 06 '19 at 21:04
  • The answer is of course "[correct horse battery staple](https://xkcd.com/936/)". I'm surprised that it wasn't already the first comment. – mivk Jul 07 '20 at 23:02
  • There's the open and free [John the Ripper](http://www.openwall.com/john/) password cracker which is a great way to check an existing password database. – tante Sep 16 '08 at 17:56

11 Answers11

16

Depending on the language, I usually use regular expressions to check if it has:

  • At least one uppercase and one lowercase letter
  • At least one number
  • At least one special character
  • A length of at least six characters

You can require all of the above, or use a strength meter type of script. For my strength meter, if the password has the right length, it is evaluated as follows:

  • One condition met: weak password
  • Two conditions met: medium password
  • All conditions met: strong password

You can adjust the above to meet your needs.

VirtuosiMedia
  • 52,016
  • 21
  • 93
  • 140
  • 4
    The diceware algorithm (http://world.std.com/~reinhold/diceware.html) does none of these except the last ("A length of at least six characters"), yet with a 6 word password has 77 bits of entropy -- pretty respectable. – Ralph Aug 25 '11 at 12:50
  • 39
    If at all possible, I avoid using any system that arbitrarily imposes most or all of the metrics defined here. To anyone designing a password strength checker, please keep in mind that some of your users have evolved from pass*word*s to pass*phrase*s. One of my throwaway passphrases is 38-characters long, all lowercase, and has 170 bits of entropy. The Sun will burn out before it could be cracked, even by our planet's fastest supercomputer. By contrast, it'd take about 15 minutes for a desktop PC to crack a password that just meets the above requirements. – Justin ᚅᚔᚈᚄᚒᚔ Jul 11 '12 at 21:51
  • 10
    I'd also like to clarify: suggesting the above metrics to the user and using a strength meter are perfectly acceptable -- just don't force me to tick off a list of required character types. [It doesn't make my password more secure and it only makes it harder to remember](http://xkcd.com/936/). Also, don't restrict me to 8 or 12 or 16 characters -- you should be storing a password hash anyway, so the length of my actual password is irrelevant; Hashing a six-character password and a 600-character passphrase with SHA1 both result in a 40-character hash 100% of the time, every time. – Justin ᚅᚔᚈᚄᚒᚔ Jul 11 '12 at 22:03
  • 1
    guessing by humans is how most accounts are compromised ... that is what the strength score is trying to prevent: human-guessable passwords. e.g., don't use a date, a name, phone number, a pet's name, etc. – Jeffrey Blattman Apr 09 '13 at 16:41
  • 11
    So by this definition 'Password' or 'Secret' or '123456' would have a strength of medium. Perfect. – MickJ Sep 23 '14 at 15:21
10

The object-oriented approach would be a set of rules. Assign a weight to each rule and iterate through them. In psuedo-code:

abstract class Rule {

    float weight;

    float calculateScore( string password );

}

Calculating the total score:

float getPasswordStrength( string password ) {     

    float totalWeight = 0.0f;
    float totalScore  = 0.0f;

    foreach ( rule in rules ) {

       totalWeight += weight;
       totalScore  += rule.calculateScore( password ) * rule.weight;

    }

    return (totalScore / totalWeight) / rules.count;

}

An example rule algorithm, based on number of character classes present:

float calculateScore( string password ) {

    float score = 0.0f;

    // NUMBER_CLASS is a constant char array { '0', '1', '2', ... }
    if ( password.contains( NUMBER_CLASS ) )
        score += 1.0f;

    if ( password.contains( UPPERCASE_CLASS ) )
        score += 1.0f;

    if ( password.contains( LOWERCASE_CLASS ) )
        score += 1.0f;

    // Sub rule as private method
    if ( containsPunctuation( password ) )
        score += 1.0f;

    return score / 4.0f;

}
user9116
  • 597
  • 3
  • 7
  • I wonder if it would make more sense to implement this in a more functional manner. Having to subclass a whole bunch of rules that are probably going to be used once seems like a lot of wasted typing. For instance create a Rule class and assign an anonymous function to its rule property for a given instance. – SapphireSun Dec 09 '09 at 07:31
  • I'm not using Java so this may or may not work in that language. – SapphireSun Dec 09 '09 at 07:31
  • I've recently implemented a password strength checker based on this suggestion. The flexibility of being able to add and tweak rules and weights allows for some swift iteration. After spending some time deriving decent rules, by comparing known good and bad passwords, I found an approach that can recognise dictionary words and proper nouns without a dictionary. – Jon Cram Oct 03 '10 at 19:32
8

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!.

Johan
  • 74,508
  • 24
  • 191
  • 319
  • Sadly, the Password Meter download is no longer available (dead domain). The "demo" site is still live, but no download to put it on your own site – Stephen R Nov 10 '22 at 17:57
  • IF using the rules outlined above, what score are you looking for? – Stephen R Nov 10 '22 at 21:14
  • Shannon entropy is not all that effective, and I'm not sure that requiring users to re-make passwords without telling them what specific metrics were violated is going to make you any friends. – Mad Physicist Jan 21 '23 at 00:23
2

Cracklib is great, and in newer packages there is a Python module available for it. However, on systems that don't yet have it, such as CentOS 5, I've written a ctypes wrapper for the system cryptlib. This would also work on a system that you can't install python-libcrypt. It does require python with ctypes available, so for CentOS 5 you have to install and use the python26 package.

It also has the advantage that it can take the username and check for passwords that contain it or are substantially similar, like the libcrypt "FascistGecos" function but without requiring the user to exist in /etc/passwd.

My ctypescracklib library is available on github

Some example uses:

>>> FascistCheck('jafo1234', 'jafo')
'it is based on your username'
>>> FascistCheck('myofaj123', 'jafo')
'it is based on your username'
>>> FascistCheck('jxayfoxo', 'jafo')
'it is too similar to your username'
>>> FascistCheck('cretse')
'it is based on a dictionary word'
Sean Reifschneider
  • 1,261
  • 10
  • 16
2

after reading the other helpful answers, this is what i'm going with:

-1 same as username
+0 contains username
+1 more than 7 chars
+1 more than 11 chars
+1 contains digits
+1 mix of lower and uppercase
+1 contains punctuation
+1 non-printable char

pwscore.py:

import re
import string
max_score = 6
def score(username,passwd):
    if passwd == username:
        return -1
    if username in passwd:
        return 0
    score = 0
    if len(passwd) > 7:
        score+=1
    if len(passwd) > 11:
        score+=1
    if re.search('\d+',passwd):
        score+=1
    if re.search('[a-z]',passwd) and re.search('[A-Z]',passwd):
        score+=1
    if len([x for x in passwd if x in string.punctuation]) > 0:
        score+=1
    if len([x for x in passwd if x not in string.printable]) > 0:
        score+=1
    return score

example usage:

import pwscore
    score = pwscore(username,passwd)
    if score < 3:
        return "weak password (score=" 
             + str(score) + "/"
             + str(pwscore.max_score)
             + "), try again."

probably not the most efficient, but seems reasonable. not sure FascistCheck => 'too similar to username' is worth it.

'abc123ABC!@£' = score 6/6 if not a superset of username

maybe that should score lower.

siznax
  • 474
  • 5
  • 7
2

The two simplest metrics to check for are:

  1. Length. I'd say 8 characters as a minimum.
  2. Number of different character classes the password contains. These are usually, lowercase letters, uppercase letters, numbers and punctuation and other symbols. A strong password will contain characters from at least three of these classes; if you force a number or other non-alphabetic character you significantly reduce the effectiveness of dictionary attacks.
David Webb
  • 190,537
  • 57
  • 313
  • 299
1

Well this is what I use:

   var getStrength = function (passwd) {
    intScore = 0;
    intScore = (intScore + passwd.length);
    if (passwd.match(/[a-z]/)) {
        intScore = (intScore + 1);
    }
    if (passwd.match(/[A-Z]/)) {
        intScore = (intScore + 5);
    }
    if (passwd.match(/\d+/)) {
        intScore = (intScore + 5);
    }
    if (passwd.match(/(\d.*\d)/)) {
        intScore = (intScore + 5);
    }
    if (passwd.match(/[!,@#$%^&*?_~]/)) {
        intScore = (intScore + 5);
    }
    if (passwd.match(/([!,@#$%^&*?_~].*[!,@#$%^&*?_~])/)) {
        intScore = (intScore + 5);
    }
    if (passwd.match(/[a-z]/) && passwd.match(/[A-Z]/)) {
        intScore = (intScore + 2);
    }
    if (passwd.match(/\d/) && passwd.match(/\D/)) {
        intScore = (intScore + 2);
    }
    if (passwd.match(/[a-z]/) && passwd.match(/[A-Z]/) && passwd.match(/\d/) && passwd.match(/[!,@#$%^&*?_~]/)) {
        intScore = (intScore + 2);
    }
    return intScore;
} 
varun
  • 4,522
  • 33
  • 28
0

I don't know if anyone will find this useful, but I really liked the idea of a ruleset as suggested by phear so I went and wrote a rule Python 2.6 class (although it's probably compatible with 2.5):

import re

class SecurityException(Exception):
    pass

class Rule:
    """Creates a rule to evaluate against a string.
    Rules can be regex patterns or a boolean returning function.
    Whether a rule is inclusive or exclusive is decided by the sign
    of the weight. Positive weights are inclusive, negative weights are
    exclusive. 


    Call score() to return either 0 or the weight if the rule 
    is fufilled. 

    Raises a SecurityException if a required rule is violated.
    """

    def __init__(self,rule,weight=1,required=False,name=u"The Unnamed Rule"):
        try:
            getattr(rule,"__call__")
        except AttributeError:
            self.rule = re.compile(rule) # If a regex, compile
        else:
            self.rule = rule  # Otherwise it's a function and it should be scored using it

        if weight == 0:
            return ValueError(u"Weights can not be 0")

        self.weight = weight
        self.required = required
        self.name = name

    def exclusive(self):
        return self.weight < 0
    def inclusive(self):
        return self.weight >= 0
    exclusive = property(exclusive)
    inclusive = property(inclusive)

    def _score_regex(self,password):
        match = self.rule.search(password)
        if match is None:
            if self.exclusive: # didn't match an exclusive rule
                return self.weight
            elif self.inclusive and self.required: # didn't match on a required inclusive rule
                raise SecurityException(u"Violation of Rule: %s by input \"%s\"" % (self.name.title(), password))
            elif self.inclusive and not self.required:
                return 0
        else:
            if self.inclusive:
                return self.weight
            elif self.exclusive and self.required:
                raise SecurityException(u"Violation of Rule: %s by input \"%s\"" % (self.name,password))
            elif self.exclusive and not self.required:
                return 0

        return 0

    def score(self,password):
        try:
            getattr(self.rule,"__call__")
        except AttributeError:
            return self._score_regex(password)
        else:
            return self.rule(password) * self.weight

    def __unicode__(self):
        return u"%s (%i)" % (self.name.title(), self.weight)

    def __str__(self):
        return self.__unicode__()

I hope someone finds this useful!

Example Usage:

rules = [ Rule("^foobar",weight=20,required=True,name=u"The Fubared Rule"), ]
try:
    score = 0
    for rule in rules:
        score += rule.score()
except SecurityException e:
    print e 
else:
    print score

DISCLAIMER: Not unit tested

SapphireSun
  • 9,170
  • 11
  • 46
  • 59
-1

What is the best way of ensuring that a user supplied password is a strong password in a registration or change password form?

Don't evaluate complexity and or strength, users will find a way to fool your system or get so frustrated that they will leave. That will only get you situations like this. Just require certain length and that leaked passwords aren't used. Bonus points: make sure whatever you implement allows the use of password managers and/or 2FA.

Ardent Coder
  • 3,777
  • 9
  • 27
  • 53
Braiam
  • 1
  • 11
  • 47
  • 78
-1

Password strength checkers, and if you have time+resources (its justified only if you are checking for more than a few passwords) use Rainbow Tables.

Mostlyharmless
  • 2,285
  • 19
  • 28
-1

With a series of checks to ensure it meets minimum criteria:

  • at least 8 characters long
  • contains at least one non-alphanumeric symbol
  • does not match or contain username/email/etc.
  • etc

Here's a jQuery plugin that reports password strength (not tried it myself): http://phiras.wordpress.com/2007/04/08/password-strength-meter-a-jquery-plugin/

And the same thing ported to PHP: http://www.alixaxel.com/wordpress/2007/06/09/php-password-strength-algorithm/

Peter Boughton
  • 110,170
  • 32
  • 120
  • 176