Debian GNU/Linux · Accounts & authentication

Enforce password quality with pam_pwquality

When it fails: Users can set short or trivial passwords. Weak local passwords are guessed quickly, online or from a stolen shadow file.

CB-DEB-AUTH-010mediumactiveautomatedautomated fixprofile core

As released in v0.1.1 beta · pre-release. Current state.

Description

pam_pwquality (package libpam-pwquality) checks every new password when it is set with passwd. Its settings live in /etc/security/pwquality.conf and /etc/security/pwquality.conf.d/:

  • minlen: minimum length;
  • minclass: minimum number of character classes (lower, upper, digit, other);
  • difok: characters that must differ from the old password;
  • maxrepeat / maxsequence: longest run of the same or consecutive characters;
  • dictcheck: reject words from the cracklib dictionary;
  • enforce_for_root: apply the rules when root sets a password too.

Without libpam-pwquality, Debian only applies the weak built-in obscure check of pam_unix. Installing the package enables the module in /etc/pam.d/common-password through pam-auth-update.

Rationale

Length is what makes a password resist guessing. The other rules reject the patterns attackers try first (repeated characters, sequences, dictionary words, a one-character change).

Impact

Users must pick passwords that follow the rules when they next change them. Existing passwords are not checked.

Setting

Location/etc/security/pwquality.conf.d/60-cirsec.conf
DefaultModule not installed
Recommendedminlen >= {{ minlen }}, minclass >= {{ minclass }}, difok >= 2, maxrepeat <= 3, maxsequence <= 3, dictcheck, enforce_for_root

Parameters

ParameterCoreServerServer hardenedDesktopDesktop hardenedBounds
minleninteger. Minimum password length.14141614168 to 64
minclassinteger. Minimum number of character classes.333330 to 4

The value used is your override (tailoring file), else the profile's, else the one of the profile it extends, else the default. Bounds limit what an override may set.

Audit

  1. dpkg -s libpam-pwquality must show the package installed.
  2. /etc/pam.d/common-password must contain a pam_pwquality.so line.
  3. The last value of each setting in /etc/security/pwquality.conf then /etc/security/pwquality.conf.d/*.conf must be: minlen >= {{ minlen }}, minclass >= {{ minclass }}, difok >= 2, maxrepeat and maxsequence between 1 and 3, dictcheck not 0, and enforce_for_root present.

Automated: every check must pass. Scanners report each check by its id.

package

name: libpam-pwquality
state: installed

module

pam_pwquality is in the password stack.

path: /etc/pam.d/common-password
pattern: ^\s*password\s+.*pam_pwquality\.so

settings

Effective pwquality settings.

script: |
  files="/etc/security/pwquality.conf $(ls /etc/security/pwquality.conf.d/*.conf 2>/dev/null)"
  get() { cat $files 2>/dev/null | sed -n "s/^[[:space:]]*$1[[:space:]]*=[[:space:]]*\([-0-9]*\).*/\1/p" | tail -n 1; }
  minlen=$(get minlen) minclass=$(get minclass) difok=$(get difok)
  maxrepeat=$(get maxrepeat) maxsequence=$(get maxsequence) dictcheck=$(get dictcheck)
  root=$(cat $files 2>/dev/null | grep -c '^[[:space:]]*enforce_for_root')
  echo "minlen=${minlen:-9} minclass=${minclass:-0} difok=${difok:-1} maxrepeat=${maxrepeat:-0} maxsequence=${maxsequence:-0} dictcheck=${dictcheck:-1} enforce_for_root=$root"
  [ "${minlen:-9}" -ge "$CB_MINLEN" ] && [ "${minclass:-0}" -ge "$CB_MINCLASS" ] && [ "${difok:-1}" -ge 2 ] \
    && [ "${maxrepeat:-0}" -ge 1 ] && [ "${maxrepeat:-0}" -le 3 ] \
    && [ "${maxsequence:-0}" -ge 1 ] && [ "${maxsequence:-0}" -le 3 ] \
    && [ "${dictcheck:-1}" != 0 ] && [ "$root" -gt 0 ]

Remediation

  1. Run apt install libpam-pwquality.
  2. Create /etc/security/pwquality.conf.d/60-cirsec.conf:
    minlen = {{ minlen }}
    minclass = {{ minclass }}
    difok = 2
    maxrepeat = 3
    maxsequence = 3
    dictcheck = 1
    enforce_for_root
    
Restartnone
Disruptionlow

Rollback

Delete /etc/security/pwquality.conf.d/60-cirsec.conf. Remove the package to drop the module.

package

Generated from check package: the engine writes the fix for every output, it sets the expected value and the check runs again after.

settings

f=/etc/security/pwquality.conf.d/60-cirsec.conf
cb_backup "$f"
mkdir -p /etc/security/pwquality.conf.d
printf '%s\n' '# Written by CIRSEC Baselines CB-DEB-AUTH-010' "minlen = $CB_MINLEN" "minclass = $CB_MINCLASS" \
  'difok = 2' 'maxrepeat = 3' 'maxsequence = 3' 'dictcheck = 1' 'enforce_for_root' >"$f"
chmod 644 "$f"

Handwritten code reads parameters from CB_<NAME> environment variables. Each release turns these fixes into remediate.sh and an Ansible role per profile: dry run by default, a fix runs only when its check fails.

References

Mappings

How this control relates to the other framework's: equivalent, stricter, looser, partial or related.

Threats mitigated

People and lifecycle

Authors
Verified2026-10-18 by Sn0wAlice on 12, Proxmox VM, Debian 12 genericcloud image, kernel 6.1.0-54-cloud-amd64. Weak password rejected and strong one accepted with pamtester; minlen 14 core, 16 hardened.
Verified2026-10-18 by Sn0wAlice on 13, Proxmox VM, Debian 13 genericcloud image, kernel 6.12.113+deb13-cloud-amd64. Weak password rejected and strong one accepted with pamtester; minlen 14 core, 16 hardened.
Introduced inv0.1.0
Created, updated2026-10-18, 2026-10-18
Translationsfr