Password Cracking and Wordlist Engineering Part 6: Mask and Hybrid Attacks, and the Keyspace Math Behind Them

Parts 4 and 5 built candidates from real words, mutated by rules. This post covers the third way to generate them — masks, which build a candidate character-position by character-position from a defined charset, no wordlist involved at all — and does the arithmetic that determines when that’s the smart move instead of the desperate one.

The built-in charsets and mask syntax

-a 3 switches hashcat into mask mode. Verified directly from the installed binary’s --help output, the built-in charset placeholders:

PlaceholderCharsetSize
?la-z26
?uA-Z26
?d0-910
?s“ !”#$%&’()*+,-./:;<=>?@[]^_`{}~“
?a?l?u?d?s combined95

A mask is just those placeholders concatenated, one per output character position:

$ hashcat -m 1000 -a 3 ntlm.txt ?u?l?l?l?l?l?d?d?d?d

That mask reads as: one uppercase letter, five lowercase letters, four digits — eight characters total, structured rather than arbitrary. Custom charsets (-1, -2, -3, -4) let a mask reference a caller-defined character set instead of only the four built-in ones, referenced as ?1, ?2, ?3, ?4 in the mask string — visible directly in hashcat’s own bundled masks/hashcat-default.hcmask, which defines custom sets and chains several masks together in one file, comma-separated:

?l?d?u,?l?d,?l?d*!$@_,?1?2?2?2?2?2?2?3?3?3?3?d?d?d?d

The keyspace arithmetic

A mask’s keyspace — the total number of candidates it generates — is simply the product of each position’s charset size. That’s the entire formula, and it’s worth doing by hand at least once: ?a?a?a?a?a?a?a?a (eight fully-arbitrary positions) is 95^8 ≈ 6.63 × 10^15 — the exhaustive brute-force figure Part 2 used to show NTLM cracking that space in hours and bcrypt taking over a millennium.

Structure shrinks that number fast, because most positions stop being “any of 95 characters” and become “any of a much smaller, position-specific set.” A mask matching “one uppercase, five lowercase, one digit, one special” — ?u?l?l?l?l?l?d?s, a shape a lot of real complexity-policy-compliant passwords actually take — has keyspace:

26 × 26⁵ × 10 × 33 = 26 × 11,881,376 × 10 × 33 ≈ 1.02 × 10¹¹

That’s roughly 65,000 times smaller than the arbitrary 8-character brute force, for a search space that still covers a huge, realistic swath of “technically complies with a complexity policy” passwords. The saving isn’t from trying fewer interesting candidates — it’s from not wasting time on the ~99.998% of ?a?a?a?a?a?a?a?a’s keyspace that no complexity policy would ever produce in the first place, like eight consecutive special characters.

The bundled mask lists take this further

hashcat ships masks/8char-1l-1u-1d-1s-compliant.hcmask — verified directly, 40,824 lines, each one a distinct single-line mask. Sampling the first few:

?d?d?d?d?d?l?u?s
?d?d?d?d?d?l?s?u
?d?d?d?d?d?u?l?s
?d?d?d?d?d?u?s?l
?d?d?d?d?d?s?l?u

Every line is a different permutation of where the required character classes sit — five digits then lowercase-then-uppercase-then-special, five digits then lowercase-then-special-then-uppercase, and so on through every arrangement an 8-character password with “at least one of each class” complexity policy could actually take. Running the whole file (hashcat -a 3 hash.txt masks/8char-1l-1u-1d-1s-compliant.hcmask) exhaustively covers every shape a naive “must contain upper, lower, digit, and symbol” policy permits, at a small fraction of ?a?a?a?a?a?a?a?a’s cost, because it never wastes a single candidate on an arrangement the policy itself would reject. There’s a matching -noncompliant file (24,712 lines) covering the shapes that don’t satisfy a typical policy, useful for testing whether a “complexity required” control is actually being enforced rather than just displayed at signup.

Hybrid attacks: mask discipline applied to real words

-a 6 and -a 7 — introduced in Part 2’s attack-mode table — combine a wordlist with a mask instead of choosing one or the other:

$ hashcat -m 18200 -a 6 asrep.txt words.txt ?d?d?d?d?s

That’s a wordlist (-a 6 appends the mask to each entry) tried as <word><4 digits><1 symbol> — covering every 4-digit year and every trailing symbol against every word in the list, in one command. It’s worth comparing directly against Part 5’s custom rule file for the exact same target shape: that rule file needed one explicit rule line per year ($2 $0 $1 $9, $2 $0 $2 $0, $2 $0 $2 $1, …), because hashcat’s rule language has no loop construct. The hybrid mask ?d?d?d?d?s covers all 10,000 four-digit years and all 33 trailing symbols automatically, in a single line, at a keyspace cost of 10,000 × 33 = 330,000 candidates per wordlist word — trivial at any of Part 2’s throughput figures. Against svc-legacy-scan’s actual Sc4nn3r2019!, a wordlist containing the leetspoken base Sc4nn3r (from Part 5’s custom rule, or found directly via -1 ?l?u -2 ?d?s custom-charset masking on the suffix alone) combined with this hybrid mask finds the exact password without knowing 2019 was the specific year in advance. -a 7 runs the same idea with the mask first?d?d?d?d?s<word> — for organizations that prefix rather than suffix.

--increment: not committing to a single length up front

Nothing so far has addressed length uncertainty — every mask above assumes a fixed candidate length. --increment, confirmed against the installed binary’s --help output, removes that assumption:

$ hashcat -m 1000 -a 3 --increment --increment-min=6 --increment-max=10 ntlm.txt ?a?a?a?a?a?a?a?a?a?a

With a 10-character mask and --increment enabled, hashcat runs the 6-character prefix of that mask first, then 7, then 8, and so on up to 10 — trying shorter, cheaper keyspaces before committing to the full-length search, on the reasonable assumption that a shorter password is both more likely and far cheaper to rule out first.

Where mask attacks sit in the toolkit

Masks are the right tool exactly when there’s structural knowledge about a password’s shape without knowing the specific word inside it — a known complexity policy, a known length, a known “starts with a capital, ends with digits and a symbol” convention. They’re the wrong tool when the actual uncertainty is about vocabulary rather than shape — that’s what Parts 4 and 5’s wordlists and rules are for. Real assessments run all three: wordlist+rules first (cheapest, catches human-predictable choices fastest), then a hybrid pass for known-shape suffixes on known-vocabulary words, then a structured mask sweep like the compliant .hcmask file as a more exhaustive follow-up — exactly the order Part 7 runs them in, against the actual hashes this series has been building toward the whole time.