On Password Strength
Password generation may seem to be trival these days, as various password generators do exist. But I wanted to know if they are any good and I found two programs for the command line trying to examine the strength of a password:
-
cracklib-check
from the CrackLib project is a standalone program derived from the pam_cracklib module. - An alternative to
pam_cracklib
is passwdqc from the Openwall Project which also provides standalone programs to generate and check passwords.
pwgen
pwgen (v2.06 from 2007) is maybe the most popular one, and easy to use too. The last version has been released in 2007, let's see if it is any good:$ time pwgen -s -1 12 100000 | /usr/sbin/cracklib-check | fgrep -c -v ': OK' 20 real 0m31.722s user 0m20.072s sys 0m13.736s→ We generated 100k passwords in 32 seconds, 20 of them (0.02%) did not pass the
cracklib
test.
$ time pwgen -s -1 12 100000 | pwqcheck -1 --multi | fgrep -c -v OK: 2731 real 2m42.557s user 2m43.156s sys 0m1.668s→ We generated 100k passwords in 163 seconds, 2731 of them (2.7%) did not pass the
pwqcheck
test. Clearly, pwqcheck
seems to be much stricter. It also takes much longer to check, but this should only matter when checking thousands of passwords, as we just did.
pwqgen
pwqgen
(v1.3.0 from 2013) from the passwdqc project has a weird syntax, probably due to the fact that it's mostly used as a PAM module rather than as a standalone program. I couldn't figure out how to generate passwords of exactly 12 characters long, the following use of cut(1)
will miss passwords shorter than 12 characters:
$ seq 1 100000 | while read a; do pwqgen; done | cut -c-12 | ...So, for the sake of correctness, let's do this instead (although this takes ~3 times longer to complete):
$ i=0; time while [ $i -lt 100000 ]; do pwqgen | cut -c-12 | egrep -o '^.{12}$' && i=$((i+1)); done \ | /usr/sbin/cracklib-check | fgrep -c -v ': OK' 9 real 6m4.756s user 3m51.396s sys 1m22.528s→ We generated 100k passwords in 364 seconds, 9 of them (0.009%) did not pass the
cracklib
test. Clearly, pwqgen
is generating much better passwords than pwgen
, according to cracklib
.
And again with pwqcheck
:
$ i=0; time while [ $i -lt 100000 ]; do pwqgen | cut -c-12 | egrep -o '^.{12}$' && i=$((i+1)); done \ | pwqcheck -1 --multi | fgrep -c -v OK: 29083 real 6m16.043s user 6m0.292s sys 1m8.708s→ We generated 100k passwords in 376 seconds, 29083 of them (29%) did not pass the
pwqcheck
test. Wow. This even contradicts the finding above: while pwqgen
does seem to generate better passwords than pwgen
according to cracklib
, when checked with pwqcheck
, password quality seems to be much lower. Let's attribute that to our cut -c-12
hack and move on to another password generator:
apg
apg (v2.2.3 from 2003) hasn't had a release in 10 years and is kinda slow, since it's using /dev/random directly, for whatever reason:$ time apg -a 1 -m 12 -x 12 -n 100000 | /usr/sbin/cracklib-check | fgrep -c -v ': OK' 67 real 4m28.997s user 4m56.896s sys 0m17.712s→ We generated 100k passwords in 269 seconds, 67 of them (0.067%) did not pass the
cracklib
test.
And again with pwqcheck
:
$ time apg -a 1 -m 12 -x 12 -n 100000 | pwqcheck -1 --multi | fgrep -c -v OK: 291 real 5m15.415s user 9m30.960s sys 0m0.420s→ We generated 100k passwords in 315 seconds, 291 of them (0.29%) did not pass the
pwqcheck
test.
gpw
gpw (v0.0.19940601 from 2006) attempts to produce pronounceable passwords, so our criteria for random passwords won't hold. Let's test it anyway and see what happens:$ time gpw 100000 12 | /usr/sbin/cracklib-check | fgrep -c -v ': OK' 540 real 0m28.195s user 0m19.640s sys 0m10.756s→ We generated 100k passwords in 28 seconds, 540 of them (0.54%) did not pass the
cracklib
test.
$ time gpw 100000 12 | pwqcheck -1 --multi | fgrep -c -v OK: 100000 real 0m1.670s user 0m1.768s sys 0m0.016sWow - none of the passwords generated by
gpw
was accepted by pwqcheck
! Execution time was very fast, though :-)
makepasswd
makepasswd (v1.10 from 2013) is a Perl program, and a very fast one too:$ time makepasswd --chars=12 --count=100000 | /usr/sbin/cracklib-check | fgrep -c -v ': OK' 22 real 0m34.404s user 0m32.624s sys 0m13.428s→ We generated 100k passwords in 34 seconds, 22 of them (0.022%) did not pass the
cracklib
test.
$ time makepasswd --chars=12 --count=100000 | pwqcheck -1 --multi | fgrep -c -v OK: 12742 real 2m29.020s user 2m40.328s sys 0m0.024s→ We generated 100k passwords in 149 seconds, 12742 of them (12.74%) did not pass the
pwqcheck
test.So, in conclusion: use
pwqcheck
to check for passwords and apg
or pwgen
for password generation. To always use a password checker when generating a password, use something like this:
$ pwgen_check() { pwgen $@ | pwqcheck -1 --multi; } $ pwgen_check -s 12 10 OK: CjgR1nC4t9t5 OK: iggW9u3hMAnd OK: bMGGgqAm2WOB OK: E7fAY7fjF5KJ Bad passphrase (not enough different characters or classes for this length): FexzFoJRxpO5 OK: Dmz4VJBnUgQC OK: JnIcezRq39SY OK: EdaAK7gbBJDM OK: TUmzflKP3npZ OK: pSkPzf0fHnlwWhile the "benchmarks" above were made up as I discovered more and more password generators, I wrote a small script combining all these, generating the following results:
$ time ./password-test.sh 12 1000000 pwgen - 148 passwords (0%) failed for cracklib, runtime: 239 seconds. pwqgen - 127 passwords (0%) failed for cracklib, runtime: 1605 seconds. apg - 635 passwords (0%) failed for cracklib, runtime: 3225 seconds. gpw - 5249 passwords (0%) failed for cracklib, runtime: 188 seconds. makepasswd - 248 passwords (0%) failed for cracklib, runtime: 285 seconds. openssl - 175 passwords (0%) failed for cracklib, runtime: 5509 seconds. pwgen - 29523 passwords (2.00%) failed for pwqcheck, runtime: 1133 seconds. pwqgen - 290042 passwords (29.00%) failed for pwqcheck, runtime: 2248 seconds. apg - 3013 passwords (0%) failed for pwqcheck, runtime: 4082 seconds. gpw - 1000000 passwords (100.00%) failed for pwqcheck, runtime: 21 seconds. makepasswd - 128036 passwords (12.00%) failed for pwqcheck, runtime: 1029 seconds. openssl - 100438 passwords (10.00%) failed for pwqcheck, runtime: 6417 seconds. real 433m0.997s user 352m16.577s sys 120m52.305s