I needed to bruteforce some passwords that I happened to know that were
generated with the default mode (“pronouncable”) of
pwgen, so I spent a fair amount of time
writing software to help. It went through a whole lot of iterations
and ended up being more efficient than I had ever assumed would be possible
(although it's still nowhere near as efficient as it should ideally be).
So now I'm sharing it with you. If you have IPv6 and can reach
git.sesse.net, that is.
I'm pasting the entire README below. Remember to use it for ethical
purposes.
Introduction
============
pwbrute creates all possible pwgen passwords (default tty settings, no -s).
It matches pwgen 2.08. It supports ordering them by most common first.
Note that pwgen before 2.07 also supported a special “non-tty mode”
that was even less secure (no digits, no uppercase) which is not supported here.
To get started, do
g++ -std=c++20 -O2 -o pwbrute pwbrute.cc -ljemalloc
./pwbrute --raw --sort --expand --verbose > passwords.txt
wait for an hour or two and you're left with 276B passwords in order
(about 2.5TB). (You can run without -ljemalloc, but the glibc malloc
makes pwbrute take about 50% more time.)
pwbrute is not a finished, polished product. Do not expect this to be
suitable for inclusion in e.g. a Linux distribution.
A brief exposition of pwgen's security
======================================
pwgen is a program that is fairly widely used in Linux/UNIX systems
to generate “pronounceable” (and thus supposedly easier-to-remember)
passwords. On the surface of it, the default 8-letter passwords with
uppercase letters, lowercase letters and digits would have a password
space of
62^8 = 218,340,105,584,896 ~= 47.63 bits
This isn't enough to save you from password cracking against fast hashes
(e.g. NTLM), but it's enough for almost everything else.
However, pwgen (without -s) does by design not use this entire space.
It builds passwords from a list of 40 “phonemes” (a, ae, ah, ai, b,
c, ch, ...) in sequence, with some rules of which can come after each
others (e.g. the combination f-g is disallowed, since any consonant
phoneme must be followed by a vowel or sometimes a digit), and sometimes
digits. Furthermore, some phonemes may be uppercased (only first letter,
in case of two-letter phonemes). In all, these restrictions mean that
the number of producable passwords drop to
307,131,320,668 ~= 38.16 bits
Furthermore, if a password does not contain at least one uppercase letter
and one digit, it is rejected. This doesn't affect that many passwords,
but it's still down to
276,612,845,450 ~= 38.00 bits
You would believe that this means that to get to a 50% chance of cracking
a password, you'd need to test about ~138 billon passwords; however, the
effective entropy is much, much worse than that:
First, consider that digits are inserted (at valid points) only with
30% probability, and phonemes are uppercased (at valid points) only
with 20% probability. This means that a password like “Ahdaiy7i” is
_much_ more likely than e.g. “EXuL8OhP” (five uppercase letters),
even though both are possible to generate.
Furthermore, when building up the password from left to right, every
letter is not equally likely -- every _phoneme_ is equally likely.
Since at any given point, (e.g.) “ai” is as likely as “a”, a lot fewer
rolls of the dice are required to get to eight letters if the password
contains many dipthongs (two-letter phonemes). This makes them vastly
overrepresented. E.g., the specific password “aechae0A” has three dipthongs
and a probability of about 1 in 12 million of being generated, while
“Oozaey7Y” has only two dipthongs (but an extra capital letter) and a
probability of about 1 in 9.33 _billion_!
In all, this means that to get to 50% probability of cracking a given
pwgen password (assuming you know that it was indeed generated with
pwgen, without -s), you need to test about 405 million passwords.
Note that pwgen gives out a list of passwords and lets the user choose,
which may make this easier or harder; I've had real-world single-password
cracks that fell after only ~400k attempts (~2% probability if the user
has chosen at random, but they most likely picked one that looked more
beautiful to them somehow).
This is all known; I reported the limited keyspace in 2004 (Debian bug
#276976), and Solar Designer reported the poor entropy in CVE-2013-4441.
(I discovered the entropy issues independently from them a couple of
months later, then discovered that it was already known, and didn't
publish.) However, to the best of my knowledge, pwbrute is the first
public program that will actually generate the most likely passwords
efficiently for you.
Needless to say, I cannot recommend using pwgen's phoneme-based
passwords for anything that needs to stay secure. (I will not make
concrete recommendations beyond that; a lot of literature exists
on the subject.)
Speeding up things
==================
Very few users would want the entire set of passwords, given that the
later ones are incredibly unlikely (e.g., AB0AB0AB has a chance of about
2^-52.155, or 1 in 5 quadrillion). To not get all, you can use e.g.
-c -40, which will produce only those with more than approx. 2^-40 probability
before final rejection (roughly ~6B passwords).
(NOTE: Since the calculated probability is before final rejection of those
without a digit or uppercase letter, they will not sum to 1, but something
less; approx. 0.386637 for the default 8-letter passwords, or 2^-1.3709.
Take this into account when reading all text below.)
pwbrute is fast but not super-fast; it can generate about 80M passwords/sec
(~700 MB/sec) to stdout, of course depending on your CPUs. The expansion phase
generally takes nearly all the time; if your cracker could somehow accept the
unexpanded patterns (i.e., without --expand) for free, pwbrute would basically
be infinitely fast. (It would be possible to microoptimize the expansion,
perhaps to 1B passwords/sec/core if pulling out all the stops, but at some point,
it starts becoming a problem related to pipe I/O performance, not candidate
generation.)
Thus, if your cracker is very fast (e.g. hashcat cracking NTLM), it's suboptimal
to try to limit yourself to only pwbrute-created passwords. It's much, much
faster to just create a bunch of legal prefixes and then let hashcat try all
of them, even though this will test some “impossible” passwords.
For instance:
./pwbrute --first-stage-len 5 --raw > start5.pwd
./hashcat.bin -O -m 1000 ntlm.pwd -w 3 -a 6 start5.pwd -1 '?l?u?d' '?1?1?1'
The “combination” mode in hashcat is also not always ideal; consider using
rules instead.
If you need longer passwords than 8 characters, you may want to split the job
into multiple parts. For this, you can combine --first-stage-len with --prefix
to generate passwords in two stages, e.g. first generate all valid 3-letter
prefixes (“bah” is valid, “bbh” is not) and then for each prefix generate
all possible passwords. This requires much less RAM, can go in parallel,
and is pretty efficient.
For instance, this will create all passwords up to probability 2^-30,
over 16 cores, in a form that doesn't use too much RAM:
./pwbrute -f 3 -r -s -e | parallel -j 16 "./pwbrute -p {} -c -30 -s 2>/dev/null | zstd -6 > up-to-30-{}.pwd.zst"
You can then use the included merge.cc utility to merge the sorted files
into a new sorted one (this requires not using pwbrute --raw, since merge
wants the probabilities to merge correctly):
g++ -O2 -o merge merge.cc -lzstd
./merge up-to-30-*.pwd.zst | pv | pzstd -6 > up-to-30.pwd.zst
merge is fairly fast, but not infinitely so. Sorry.
Beware, zstd uses some decompression buffers that can be pretty big per-file
and there are lots of files, so if you put the limit lower than -30,
consider merging in multiple phases or giving -M to zstd, unless you want to
say hello to the OOM killer half-way into your merge.
As long as you give the --sort option to pwbrute, it is designed to give exactly
the same output in the same order every time (at the expense of a little bit of
speed during the pattern generation phase). This means that you can safely resume
an aborted generation or cracking job using the --skip=NUM flag, without worrying
that you'd lose some candidates.
Here are some estimated numbers for various probability cutoffs, and how much
of the probability space they cover (after correction for rejected passwords):
p >= 2^-25: 78,000 passwords ( 0.00% coverage, 0.63% probability)
p >= 2^-26: 171,200 passwords ( 0.00% coverage, 1.12% probability)
p >= 2^-27: 3,427,100 passwords ( 0.00% coverage, 9.35% probability)
p >= 2^-28: 5,205,200 passwords ( 0.00% coverage, 12.01% probability)
p >= 2^-29: 8,588,250 passwords ( 0.00% coverage, 14.17% probability)
p >= 2^-30: 24,576,550 passwords ( 0.01% coverage, 19.23% probability)
p >= 2^-31: 75,155,930 passwords ( 0.03% coverage, 27.58% probability)
p >= 2^-32: 284,778,250 passwords ( 0.10% coverage, 43.81% probability)
p >= 2^-33: 540,418,450 passwords ( 0.20% coverage, 55.14% probability)
p >= 2^-34: 808,534,920 passwords ( 0.29% coverage, 60.49% probability)
p >= 2^-35: 1,363,264,200 passwords ( 0.49% coverage, 66.28% probability)
p >= 2^-36: 2,534,422,340 passwords ( 0.92% coverage, 72.36% probability)
p >= 2^-37: 5,663,431,890 passwords ( 2.05% coverage, 80.54% probability)
p >= 2^-38: 11,178,389,760 passwords ( 4.04% coverage, 87.75% probability)
p >= 2^-39: 16,747,555,070 passwords ( 6.05% coverage, 91.55% probability)
p >= 2^-40: 25,139,913,440 passwords ( 9.09% coverage, 94.25% probability)
p >= 2^-41: 34,801,107,110 passwords ( 12.58% coverage, 95.91% probability)
p >= 2^-42: 52,374,739,350 passwords ( 18.93% coverage, 97.38% probability)
p >= 2^-43: 78,278,619,550 passwords ( 28.30% coverage, 98.51% probability)
p >= 2^-44: 111,967,613,850 passwords ( 40.48% coverage, 99.25% probability)
p >= 2^-45: 147,452,759,450 passwords ( 53.31% coverage, 99.64% probability)
p >= 2^-46: 186,012,691,450 passwords ( 67.25% coverage, 99.86% probability)
p >= 2^-47: 215,059,885,450 passwords ( 77.75% coverage, 99.94% probability)
p >= 2^-48: 242,726,285,450 passwords ( 87.75% coverage, 99.98% probability)
p >= 2^-49: 257,536,845,450 passwords ( 93.10% coverage, 99.99% probability)
p >= 2^-50: 268,815,845,450 passwords ( 97.18% coverage, 100.00% probability)
p >= 2^-51: 273,562,845,450 passwords ( 98.90% coverage, 100.00% probability)
p >= 2^-52: 275,712,845,450 passwords ( 99.67% coverage, 100.00% probability)
p >= 2^-53: 276,512,845,450 passwords ( 99.96% coverage, 100.00% probability)
all: 276,612,845,450 passwords (100.00% coverage, 100.00% probability)
License
=======
pwbrute is Copyright (C) 2025 Steinar H. Gunderson.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.