Implementing a password policy

Installing rkhunter
By using the built in PAM (Pluggable Authentication Module), login and other tools such as chage, it's possible to increase the password security of your systems. chage is a tool that allows to change the user password expiry information. It's important to remember that safe but longer passwords take more time to crack then shorter, complex passwords. We were thought to learn how to use passwords with numbers and special characters but these make it harder to remember possibly leading to a security risk because of people writing the passwords down as they can't remember their passwords.

This image is from xkcd and explains it well:

Password strength according to xkcd

There are a number of interesting password characteristics or related topics such as locked account etc.:

  • length
  • complexity
    • uppercase letters
    • lowercase letters
    • special characters
  • password validity: how long the password is valid
  • account expiration date
  • minimum time between password changes
  • maximum time between password changes
  • when to warn about a needed password change
  • number of times a user can try to login

Some of these settings are done in /etc/pam.d, others are done in /etc/login.defs.


PAM overrides some of the settings from /etc/login.defs so always check before changing a setting.


On Debian the minimum password length is 6 characters. However, with PAM it isn't very trivial to find this information. Have a look at the file /etc/pam.d/common-password:

password        [success=1 default=ignore] obscure sha512

No length specified... The length is specified in the pam_unix code. Furthermore, the obscure keyword already performs some checks on the password strength:

        Enable some extra checks on password strength. These checks are based on the "obscure" checks in the original shadow
        package. The behavior is similar to the pam_cracklib module, but for non-dictionary-based checks. The following checks are

                Verifies that the new password is not a palindrome of (i.e., the reverse of) the previous one.

        Case Change Only
                Verifies that the new password isn't the same as the old one with a change of case.

                Verifies that the new password isn't too much like the previous one.

                Is the new password too simple? This is based on the length of the password and the number of different types of
                characters (alpha, numeric, etc.) used.

                Is the new password a rotated version of the old password? (E.g., "billy" and "illyb")

If you want to specify the minimum length, for instance minimum 10 characters:

vi /etc/pam.d/common-password
password        [success=1 default=ignore] obscure sha512 **minlen=10**

There are also PAM modules that can be used such as libpam-cracklib that allow you to change these settings. Another such module is libpam-pwquality Check the respective man pages to find out what features are supported. We wil use libpam-cracklib:

apt install libpam-cracklib



cracklib allows one to specify additional settings for the password complexity. pam_cracklib is hardcoded to never let users pick passwords with less than 6 characters. To use cracklib, edit /etc/pam.d/common-password and consider the following line:

vi /etc/pam.d/common-password
password requisite minlen=10 lcredit=0 ucredit=0 dcredit=0 ocredit=0

Check the manpage for pam_cracklib for more information.

The minlen parameter is more than just the password length. There is such a things as credits that count towards the length. Credits for using the specified number of upper, lower, digit and other characters are used to calculate if the minlength has been achieved.

  • If you set the number >= 0, the specified number is the maximum credit for having the specified type of characters in your password.
  • If the number is < 0, this is the number of characters of this type you need to have in the password.
lcredit: lowercase letters
ucredit: uppercase letters
dcredit: digits
ocredit: other characters

By default you get 1 credit for each type/class of character.

For example, if you want a password of length 10 and want at least 1 uppercase, 1 digit and 1 other character:

password requisite minlen=10 lcredit=0 ucredit=-1 dcredit=-1 ocredit=-1

Another example:

password requisite minlen=10 lcredit=1 ucredit=1 dcredit=1 ocredit=1

If a user uses a lowercase, uppercase, digit and another character, and minimum password lenght of 6 characters is needed.

We can also specify how many characters the new passwords must have that are different from the previous password. This is done with the difok parameter. It's value is 5 by default.

If you want to define the number of different classes (lower, upper letters, digits and other characters) that the user needs to use, use the minclass parameter.

To reject a password that uses the username, specify reject_username

A last interesting parameter is the retry parameter, it's default value is 1. This mean the user is prompted 1 time before an error is returned.

Dictionary checks

cracklib uses dictionaries to check the password. The dictionaries are located in /var/cache/cracklib/. They contain words that are easy to guess and shoudln't be used in a password. These dictionaries are rebuilt nightly by the /usr/sbin/update-cracklib script.

To add words of your own, you need to create a word list file. One word per line, save it to one of these folders. :

  • /usr/share/dict
  • /usr/dict
  • /usr/local/share/dict
  • /usr/local/dict

Check or modify /etc/cracklib/cracklib.conf for the locations.

After creating such a word list file, usethe create-cracklib-dict command to convert the word lists into cracklib dictionaries. The results are placed in the default compiled-in dictionary location /var/cache/cracklib/. Or use update-cracklib to build a compressed and accumulated version of the wordlists in those source directories.

Account expiration

We can specify when a password expires and when the account expires. However calculating exactly when a date is, can be tricky. To do so, use the date command. For example if you want an account that's valid for 3 months:

date -d "3 months"
Fri Jul 14 11:38:07 EDT 2018

Or 30 days:

date -d "30 days"
Sun May 14 11:40:37 CEST 2018

New accounts

To specify the expiration date, use the -e, --expiredate of the useradd command. Date is in the format YYYY-MM-DD. If you don't specify an expiration date, useradd takes the default. Default is defined here:

vi /etc/default/useradd
# The default expire date

Adding a user:

useradd -e 2018-04-14 <user>

When using the date to calculate the expiration date for you:

useradd -e `date -d "3 months" +"%Y-%m-%d"` <user>

Existing accounts

Set the expiration date on an existing account:

chage -E 'YYYY-MM-DD' <user>

When using date to calculate the expiration date for you:

chage -E `date -d "3 months" +"%Y-%m-%d"` <user>

To display the account expiration info for a user:

chage -l <user>
Last password change                                    : mrt 19, 2018
Password expires                                        : never
Password inactive                                       : never
Account expires                                         : never
Minimum number of days between password change          : 0
Maximum number of days between password change          : 99999
Number of days of warning before password expires       : 7

Lock account

You can immediately lock an account with the --lock parameter of usermod:

usermod --lock --expiredate 1 <user>


-- lock is used to lock a user's password by putting a '!' in fromt on the encrypted password. If you wish to lock the account (not only access with a password), you should also set the EXPIRE_DATE to 1

You can use the above to lock accounts after a set period of time. This is usefull when dealing with periods < 1 day. For instance if you want an account to be open for say 1h, you can use the expiration date as done above because the account will be open longer than only 1 hour.

You can use a cron job to lock the account. Or use the at command. For instance, after we have created an account or activated an account again, we want it to be locked after 1 hour:

echo usermod --lock --expiredate 1 <user> | at now + 1 hour

Before issuing this command, a users "Account Expires" info:

chage -l <user>
Account expires: never


Account expires: jan 02, 1970

To unlock or enable the account again:

usermod -e "" <user>

Another interesting settings is how many login tries the system allows. In /etc/login.defs we find the following:


Above there is a note stating the following:


Max number of login retries if password is bad. This will most likely be overriden by PAM, since the default pam_unix module has it's own built in of 3 retries. However, this is a safe fallback in case you are using an authentication module that does not enforce PAM_MAXTRIES.

Apparently, PAM_MAXTRIES is a return value a PAM authentication module can return. But how do we know which one is set and what the value is, or if the setting in /etc/login.defs is used or the PAM_MAXTRIES setting?

In any case, set the value to be safe.

We can use the PAM module pam_tally2, which is part of the package libpam-modules. If it isn't already installed, install it:

apt install libpam-modules

pam_tally2 increments an attempted login counter and checks if a user should be denied access. To set the number of login attempts to 5:

vi /etc/pam.d/common-auth
auth    required deny=5
account required

To check the login counter:

pam_tally2 -u <user>
Login           Failures Latest failure     From
<user>              0

To unlock a pam_tally2 locked user:

pam_tally2 -r -u <user>

It might be interesting to specify the account unlock time in seconds as well:

vi /etc/pam.d/common-auth
auth    required deny=5 unlock_time=60

To limit the number of times a user can be loggend into a system. For instance, limit <user> to 3 logins:

vi /etc/security/limits.conf

@<user>   -   maxlogins   3

Password expiration

To define when a password expires and when you get a warning about it, /etc/login.defs is used. From the file, rather self explanatory:

PASS_MAX_DAYS       Maximum number of days a password may be used.
PASS_MIN_DAYS       Minimum number of days allowed between password changes.
PASS_WARN_AGE       Number of days warning given before a password expires.

To set them, edit the file and use the values you find appropriate:

vi /etc/login.defs


These settings will only take effect for newly created users. To activate these settings for existing users, we need to use chage. To specify the maximum number of days between password changes:

chage -M <days> <username>

To specify the minimum number of days:

chage -m <days> <username>

To specify when the warning of an expiring password needs to be activated:

chage -W <days> <username>

To lock the account after x number of days of inactivity after a password has expired:

chage -I <days> <username>

To display the password info for a user:

chage -l <user>
Last password change                                    : mrt 19, 2018
Password expires                                        : never
Password inactive                                       : never
Account expires                                         : never
Minimum number of days between password change          : 0
Maximum number of days between password change          : 99999
Number of days of warning before password expires       : 7

Forbid previously used password

It's possible to specify the number of previous password that are remembered to prevent the user from using those passwords again. For instance, to remember the last 3 passwords:

vi /etc/pam.d/common-password
password        [success=2 default=ignore] obscure use_authtok try_first_pass sha512 remember=5

Password score

A simple tool to test the password quality is pwscore. It's part of the libpwquality-tools package:

apt install libpwquality-tools

It either reports an error if the password fails any of the checks, or it prints out the password quality score as an integer value between 0 and 100. Values below 50 can be treated as moderate quality and above it fairly strong quality.

To use it, run the tool with the username as optional argument. You will need to specify the password.:

pwscore <user>

If uses libpwquality and as such, also uses etc/security/pwquality.conf. Use that file to specify your password policy settings.

As you can see, a lot of info and there is even a lot of topics not discussed here but it's a start. Adding additional layers of security is almost always a good thing.