The -a 9 association attack guesses each hash using the information stored next to that hash. It is for when something about the account is already known: a username, a filename, a hint, a network name, anything that may have gone into the password.
Every other attack tries every candidate against every hash. This one pairs them up. The first word goes to the first hash and nowhere else. A file of a million accounts is a million small attacks rather than one enormous one, so the cost is the number of hashes times the guesses per hash, not times the size of a wordlist.
People put themselves into their passwords. An account called j.smith has smith123 and Jsmith2024 within a few thousand guesses. No wordlist finds those any faster than it finds anything else, because a wordlist does not know which line belongs to which hash.
hashcat takes the words out of the file itself. This is usually what you want:
$ hashcat -m 500 -a 9 users.hash
Each line is split at the first :. What is in front is the username, what is behind is the hash:
alice:1a1dc91c907325c69271ddf0c944bc72:s0 j.smith:e10adc3949ba59abbe56e057f20f883e:s1
Use -p if your file uses a different separator.
Some hash modes carry their own words and need no username at all. A WPA capture has no accounts, but it does have the network name and two MAC addresses, so this just works:
$ hashcat -m 22000 -a 9 handshakes.hc22000
Here hashcat pairs the two files by line number:
$ hashcat -m 500 -a 9 hashes.txt words.txt
Line 3 of the wordlist is tried against line 3 of the hash file and against nothing else. The two files must have the same number of lines, and hashcat refuses the run if they do not.
Given only a hash file, j.smith produces these, in this order:
smith the longest run of letters, usually the surname jsmith every run of letters joined together j.smith the name exactly as written
There is no j in that list. Anything shorter than three characters is dropped, because an initial is not a password and every extra hint costs a round for every account in the file. The name as written is the exception, so a short name still works: jo gives just jo.
A run of digits comes after the letters, and the joined form is letters only, so user2024 gives user. Names are also split at case boundaries, so JEdgarHoover gives Edgar and Hoover, and the J is dropped for the same reason as the j.
A /etc/shadow style line gives more, because the real name and home directory are there too:
jsmith:$1$...:1000:1000:John Smith,,,:/home/jsmith:/bin/bash
That produces jsmith, Smith, JohnSmith, John and John Smith.
A phase is one way of turning those words into candidates. All three run by default, cheapest first, so a run you stop early has spent its time on the guesses most likely to land.
| phase | what it does | does it end? |
|---|---|---|
words | every word as it stands | yes |
rules | every word through a rule list | yes |
pcfg | every word through a probability-ordered grammar | no |
words is a few guesses per account. A password that is just the surname falls here.rules runs the first 1000 rules of rules/rockyou-30000.rule, which is ordered by how often each rule has won.pcfg wraps things around each of the account's own words, so smith reaches smith123, Smith2024, SMITH!, 1smith and smithsmith, ordered by how likely those shapes are.
The grammar has no end, so a default -a 9 run does not finish on its own. If you want a run that completes, say which phases you want:
$ hashcat -m 500 -a 9 users.hash phases=words,rules
That is every word, then every word through a thousand rules, then done.
-a 9 works with rules. Rules you pass with -r apply after the active phase, so they multiply what the phase produced rather than replacing it.-S.
The full reference is docs/hashcat-association.md in the hashcat source.
This forum post goes into more details: