Mask attacks and keyspace: brute force that actually finishes
Build hashcat masks with charsets, do the keyspace math, use custom charsets and increment, and know when -a 3 beats a wordlist and when it is hopeless.
A wordlist asks "is the password one of these known strings?" A mask asks a different question: "is the password this shape?" When you know the shape and you do not know the string, the mask is the better tool, and it is the only attack that will ever exhaustively cover a keyspace and tell you for certain the password is not in it.
The whole game is keeping that keyspace small enough to finish.
Charsets and masks
Hashcat ships four built-in charsets, plus a catch-all:
?l abcdefghijklmnopqrstuvwxyz (26)
?u ABCDEFGHIJKLMNOPQRSTUVWXYZ (26)
?d 0123456789 (10)
?s space and symbols (33)
?a ?l?u?d?s (95)
A mask is a string of these placeholders, one per character position. Each position expands to its charset. So this mask:
?u?l?l?l?l?l?d?d
describes an eight-character password that starts with one uppercase, has five lowercase, and ends in two digits. Sunday24, Monkey99, Castle10. You run it with attack mode 3:
hashcat -m 0 -a 3 hashes.txt ?u?l?l?l?l?l?d?d
-a 3 is the brute-force / mask mode. Literal characters are allowed too: Pass?d?d?d?d fixes the word Pass and brute-forces four trailing digits.
The keyspace math, and why it bites
Every position multiplies. The total candidate count is the product of each position's charset size.
Take the mask above. One ?u (26), five ?l (26 each), two ?d (10 each):
26 × 26^5 × 10^2 = 26^6 × 100 = 308,915,776 × 100 = 30,891,577,600
About 30.9 billion candidates. On a GPU doing 30 billion MD5 per second, that is one second. Fine.
Now make every position ?a (95) and stretch to eight characters:
95^8 = 6,634,204,312,890,625
6.6 quadrillion. At 30 billion per second that is about 61 hours. Add one more ?a position and you multiply by 95 again: 95^9 is roughly 240 days. Each added position of full charset multiplies the cost by 95. This is why "just brute-force it" stops being a plan around nine random characters on a fast hash, and stops being a plan at all on a slow hash.
The lesson is mechanical: narrow charsets and shorter masks are the entire art. Every ?a you can downgrade to ?l or ?d shrinks the run by a huge factor.
Custom charsets
When the built-ins are too broad, define your own with -1 through -4. Say you know passwords are lowercase letters and digits only:
hashcat -m 0 -a 3 -1 ?l?d hashes.txt ?1?1?1?1?1?1?1?1
-1 ?l?d builds a custom set of 36 characters, and ?1 references it. That eight-position run is 36^8 ≈ 2.8 trillion, versus 95^8 ≈ 6.6 quadrillion for full ?a. Same length, roughly 2000x less work, because you used what you knew.
Increment and hybrid attacks
You rarely know the exact length. --increment walks the mask from short to long:
hashcat -m 0 -a 3 --increment --increment-min 6 --increment-max 8 hashes.txt ?a?a?a?a?a?a?a?a
That tries length 6, then 7, then 8, cheapest first, so you crack the short ones before committing GPU time to the long tail.
Then there are hybrid modes, which bolt a mask onto a wordlist. This is where masks earn their keep against real human passwords, because people take a base word and append junk. Attack mode 6 is wordlist + mask:
hashcat -m 0 -a 6 hashes.txt rockyou.txt ?d?d?d?d
Every word in rockyou gets four digits appended. password2024, summer1999. Attack mode 7 is the reverse, mask + wordlist, for prepended patterns:
hashcat -m 0 -a 7 hashes.txt ?d?d?d?d rockyou.txt
Hybrids catch the Word + suffix pattern that pure wordlists miss and pure masks would take forever to reach. Pair them with a solid wordlist and rules setup and you cover most of how humans actually mangle passwords.
When the mask is the right call, and when it is not
Masks win when you have structure. A known complexity policy is structure you can weaponize. If the domain enforces "minimum 8, at least one uppercase, at least one digit," a huge swath of users produce exactly ?u?l?l?l?l?l?l?d: capital first, lowercase body, digit last. That single mask covers an enormous fraction of policy-compliant passwords and finishes in seconds on a fast hash. The policy meant to add strength handed you the keyspace.
Other structures: phone numbers (?d masks of fixed length), serial-style passwords, PINs, anything where the format is predictable. In all of these a tight mask beats a wordlist because it generates exactly the candidates that can exist and never wastes a guess.
Masks lose when there is no structure to exploit. A genuinely random nine-plus-character password on a fast hash is past the horizon, and on bcrypt or Argon2 even a six-character random mask is out of reach because the hash rate is thousands per second, not billions. Throwing ?a?a?a?a?a?a?a?a at a bcrypt hash is not an attack, it is a way to heat your GPU for the rest of the decade.
So before you launch, two checks. Confirm you are even on the right hashcat mode, because a mask against the wrong mode burns time for nothing. Then do the multiplication in your head: charset sizes times each other, divided by your hash rate. If the answer is days, you tighten the mask or you walk away. The math does not negotiate.