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.
Let's use these two programs on a few password generators. For the sake of simplicity, let's only generate random looking passwords that are exactly 12 characters long. Nobody should have to type passwords these days anyway and a password manager is recommended.

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.016s
Wow - 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: pSkPzf0fHnlw
While 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