Using CrackLib to require stronger passwords

Let's face it, humans are not well adapted to memorizing strings of random characters; and hence, the average computer user is not very good at creating secure passwords. Most users create passwords made up of easy-to-remember words, like the name of a favorite sports team or maybe the name of a significant other. In this article, I will show you how you can make Django require better passwords from your users.

Here are the packages you will need:

Installing the required packages

Step 1: Install CrackLib.

CrackLib is a library for checking that passwords are not easily crackable, or in other words, it makes sure that a password is not based on a simple character pattern or on a dictionary word. CrackLib is a common package that you should be able to find in your distribution's package manager (or, quite possibly, it could already be installed). Alternatively, you could download the source and follow the installation instructions in the README file. By default, CrackLib installs a python package named cracklib, but it does not have as many features as python-crack. (Redhat does not include the cracklib python package in its cracklib package.)

Step 2 (optional, but recommended): Install extra word dictionaries.

Packaged with CrackLib is a file name cracklib-small. (Some distributions, like RedHat, don't include this file in their cracklib package, in which case keep reading...) This file is a dictionary of words, simply a long list of words with one word per line. These 50,000 words are a good start, but we can do better. On the CrackLib download page, there is also a package named cracklib-words. After downloading and extracting the package, you will have a single file containing 1,648,379 "words". Many distributions also have a cracklib-dicts or cracklib-words package that maybe the same or similar to the cracklib-words file on the CrackLib website.

For even more dictionaries, take a look at these word lists. It might also be a good idea to create your own list of words. For example, if you work for a company in the financial industry, it would be a good idea to make a list of words and slang specific to that industry. If you will be dealing with non-english speaking users, it would be a good idea to find some dictionaries in other languages.

Step 3: Create the word indexes that get used by CrackLib.

Now that we have our dictionaries, we need to compile them into an index that CrackLib uses. So, put all your dictionaries in one directory (/usr/share/dict/ is a common place). Depending on your distribution, there will be different commands to run. On Gentoo (or if you installed from source), the following (as root) should do the trick:

create-cracklib-dict /usr/share/dict/*

And on RedHat:

mkdict /usr/share/dict/* | packer /usr/lib/cracklib_dict

These commands should create the following files:

/usr/lib/cracklib_dict.hwm
/usr/lib/cracklib_dict.pwd
/usr/lib/cracklib_dict.pwi

Step 4: Install python-crack

If your distribution's package manager doesn't include python-crack, then you will have to download and install it yourself. Neither Gentoo nor RedHat include this package, so I will walk you through the install. Download and unpack python-crack. Now, cd into the directory and run:

./configure

By default, configure will setup python-crack's files to be installed to /usr/local. This means that you will have to add /usr/local/lib/python2.4/site-packages to your PYTHONPATH. Alternatively, you could add a --prefix=/usr to the end of the configure command to install python-crack's files to the usual /usr/lib/python2.4/site-packages directory.

If you get this error:

checking for prefix of the default cracklib dictionary database... unknown
configure: error: crack.h does not define CRACKLIB_DICTPATH. Please use DEFAULT_DICTPATH, see ./configure --help

then set the DEFAULT_DICTPATH variable when running configure, like so:

./configure DEFAULT_DICTPATH=/usr/lib/cracklib_dict

Now run:

make
make install

Now that we are finished installing, let's test out python-crack:

$ python
Python 2.4.3 (#1, Jun 28 2006, 23:41:37)
[GCC 3.4.6 (Gentoo 3.4.6-r1, ssp-3.4.5-1.0, pie-8.7.9)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import crack
>>> crack.VeryFascistCheck('foo')
Traceback (most recent call last):
...
ValueError: it is WAY too short
>>> crack.VeryFascistCheck('foobar')
Traceback (most recent call last):
...
ValueError: it is based on a dictionary word
>>> crack.VeryFascistCheck('3#hsad2U>u2u')
'3#hsad2U>u2u'

VeryFascistCheck() raises a value error and prints an explanation if the password is not strong enough, otherwise it echos back the password.

Creating the Django Manipulator

from django import forms

class NewUserForm(forms.Manipulator):
    def __init__(self):
        self.fields = [
            forms.TextField(field_name="username", length=20,
                            maxlength=20, is_required=True),
            forms.PasswordField(field_name="password",
                length=20, maxlength=50, is_required=True,
                validator_list=[isStrongPassword]),
            forms.PasswordField(field_name="confirm_password",
                length=20, maxlength=50, is_required=True,
                validator_list=[
                    forms.validators.RequiredIfOtherFieldGiven(
                        "password", "You must confirm password"),
                    forms.validators.AlwaysMatchesOtherField(
                        "password", "Passwords did not match"),
                ]
            ),
        ]

def isStrongPassword(field_data, all_data):
    """Test the password with cracklib to make sure it is strong."""

    import crack
    # Increase the number of credits required from
    # the default of 8 if you want.
    #crack.min_length = 11
    try:
        crack.VeryFascistCheck(field_data)
    except ValueError, message:
        raise forms.validators.ValidationError, \
            "Password %s." % str(message)[3:]

Notice that I added isStrongPassword to the password field's validator_list. Now you are ready to put the NewUserForm manipulator to use in your views.py. After running the manipulator's get_validation_errors(), bad passwords will generate errors such as "Password is based on a dictionary word" or "Password does not contain enough DIFFERENT characters." For help on using manipulators in your view, see the Forms, fields, and manipulators documentation.

If you want to make Django's built-in authentication require stronger passwords, then you could add validator_list=[isStrongPassword] to the password field of django.contrib.auth.models.User.

Now, go forth and require strong passwords.