Dictionary attack — Comprehensive operation
Project reference document Time2Crack
Recipients: developers, security researchers, advanced users
Contents
isDictWord()addDictionaryAttacks()applyHighFidelityCalibration()1. Overview
Dictionary attack is the oldest password cracking method, the simplest conceptually, and statistically most effective on the majority of real human passwords. Its principle is to test, in descending order of probability, a pre-compiled list of candidates — the "dictionary" — rather than exhaustively enumerate all possible combinations as the raw force does.
Why it's dreadfully effective Humans do not choose their passwords randomly. Decades of data leaks reveal that the actual distribution of passwords is extremely concentrated: the most common 1,000 passwords represent about 5 to 10% of all online accounts. The most common 1 million cover 40 to 60%. A well-built dictionary attack will test these candidates in milliseconds to seconds — where gross force would take millions of years.In Time2Crack, the dictionary attack models two distinct but complementary mechanisms:
These two detections are independent and complementary: HiBP covers the exact compromise credentials, the wordlist covers the common words not yet leaked.
2. Historical and academic background
2.1 Origins
The dictionary attack precedes the modern era of computer security. The first Unix systems stored passwords in plain language in /etc/passwd, making any dictionary attack trivial as soon as the file was accessible.
2.2 Founding leaks
Some leaks have transformed the landscape of the attack by dictionary by revealing the actual distribution of human passwords:
RockYou (2009) A developer had stored 14.3 million passwords in plain language.- Password #1 ("123456") represented 290,731 accounts (2%)
2.3 Reference Benchmarks for Dictionary Attack
A wordlist of 200,000 entries tested at the speed of a RTX 4090:
AlgorithmSpeedTime for 200k candidates ------------------------------------------------ MD5168.9 GH/s~1,2 nanosecond SHA-150.86 GH/s~3.9 nanoseconds SHA-25622.68 GH/s~8.8 nanoseconds NTLM288.5 GH/s~0,7 nanosecond bcrypt (cost 5)184 kH/s~1.09 second Argon2id~67 H/s~50 minutesThe dictionary attack is therefore almost instantaneous for MD5, SHA-1 and NTLM — and remains very fast even for bcrypt on small lists.
3. Conceptual foundations: why dictionaries work
3.1 The Power Law of Human Passwords
All empirical studies converge towards the same observation: the distribution of human passwords is not uniform — it follows a law of power (Zipf). Some passwords concentrate an disproportionate fraction of choices.
Formally : if all passwords are classified by decreasing frequency, the frequency of the rank password r is proportional to r^(-α) with α 1.5 to 2 According to the corpus.Direct consequence: 10 most common passwords An attacker who tests only 10 candidates already has a chance in 20 to succeed — statistically, it's huge for the amount of noise generated.
3.2 Cognitive Bias in Password Creation
Humans do not choose at random. They systematically apply heuristics that make their passwords predictable:
3.3 The concept of "password already in the lists"
A password may be in an attack list for two different reasons:
Reason 1 — He's in HIBP The exact credential has been compromised in a past leak. The attacker does not need to guess — it tests the known credentials one by one, in order of frequency. If the target server accepts the credential, it is finished. Reason 2 — It is in a linguistic wordlist The password is an ordinary word of common vocabulary. Even if it has never fled, it is in all attack wordlists because it is an obvious candidate. "Paillon" may not be in HiBP, but it is in the French wordlist of any serious cracking tool.These two cases correspond to attacks of a different nature, with different speeds — and this is precisely what Time2Crack models.
4. Architecture of a modern dictionary attack
4.1 General pipeline
A modern dictionary attack takes place in several phases:
Phase 1 : Constitution de la wordlist
├── Mots de passe fuités (HIBP, RockYou, Collections)
├── Wordlists linguistiques (dictionnaires, prénoms, lieux)
├── Wordlists thématiques (sport, pop culture, tech, religion)
└── Wordlists contextuelles (domaine de l'entreprise cible, noms d'employés)
Phase 2: Wordlist Preprocessing
- Sort by frequency (most likely first)
- - Deduplication (not twice the same candidate)
- - Length filtration (target length + ±2)
- - Standardization (lowercase, Unicode NFC)
Phase 3: Calculation of Candidate Hashs
For each candidate w in the wordlist:
│ hashcandidate = hashalgo(w)
Promotional │ if hashcandidate== hashTarget : FINDED
- - Speed: 168 GH/s for MD5 on RTX 4090
Phase 4: Analysis of results
Report of cracked passwords, coverage statistics
4.2 Hardware optimizations
Modern dictionary attacks are massively paralleled on GPU:
For a wordlist of 14 billion entries (HIBP size) on 12× RTX 4090:
This difference of ~9 orders of magnitude between MD5 and bcrypt explains why the choice of the hash algorithm is critical.
4.3 Test order of candidates
The wordlist is not tested in alphabetical order — it is sorted by decreasing probability :
This sort ensures that if the password is "low", it will be found in the first milliseconds — whatever the hash speed.
5. The corpus of real passwords
5.1 Have I Been Pwned (HIBP)
Size ~14 billion unique passwords (2024) Source : aggregation of public leaks since 2013 Format : SHA-1 sorted by frequency of appearance Public access : k- anonymity API, full hash download available for defendersHiBP is the global reference because:
An attacker with the complete HIBP dump can first test the ~14 billion candidates in descending order of frequency. For non-salted algorithms (MD5, SHA-1), this list covers in the order of 50-70% of all real online passwords.
5.2 RockYou (2009) — Founding corpus
Size : 14.3 million clear passwords Key characteristics : RankPasswordOccurrences% of corpus ------------------------------------------------ 1123456290 7312.03% 21234579 0780.55% 312345678976 7900.54% 4password59 4620.42% 5iloveyou49 9520.35% Top 10—~650,000~4.5% Top 100—~1 200 000~8.4% Top 1 000—~2 500 000~17.5% Top 10,000—~4 500 000~31.5% Education Test only 10,000 candidates (100 μs MD5 speed) compromises ~31% of a database of MD5 protected accounts.5.3 Collections of specialized wordlists
SecLists (Daniel Miessler): open-source collection of ~4 GB wordlists covering:These corpuses are publicly downloadable — their existence is an established fact of offensive security.
6. K-Anonymity Verification HIBP: A Real-Time Attack
6.1 Protocol k- anonymity
Time2Crack incorporates a real-time HIBP check that reproduces exactly what an attacker would do with the HIBP database — but never transmit the password in plain language or its full hash.
The k-anonymity protocol works as follows:
1. Calculer SHA-1(password) → par exemple "CBFDAC6008F9CAB4083784CBD1874F76618D2A97"
Send only the first 5 characters to the HIBP API: "CBFDA"
The API returns ALL SHA-1 suffixes starting with "CBFDA" (approximately 400-600 hashes)
Search locally if the rest of the hash ("C6008F9CAB4083784CBD1874F76618D2A97") is in the list
If found: password has been compromised (HIBP also returns the number of leaks)
Guarantee of confidentiality The HIBP server never sees the complete hash (so it cannot identify the password), let alone the clear password. The final calculation is done locally.
What this means for the attack : if HIBP returns a match with a high count (e.g. 9,547,236 appearances for "123456"), this means that an attacker with the HIBP database testing this credential would find him in a very high position on his list sorted by frequency — in a fraction of nanoseconds.
6.2 Implementation in Time2Crack (app.js)
The HIBP check is asynchronous and non-blocking. The password is immediately analyzed by local methods, and the HIBP check completes the result:
// Pseudo-code simplifié
async function checkHIBP(password) {
const sha1 = await sha1Hash(password);
const prefix = sha1.substring(0, 5); // 5 premiers chars
const suffix = sha1.substring(5); // reste du hash
const response = await fetch(
https://api.pwnedpasswords.com/range/${prefix}
);
const lines = await response.text();
for (const line of lines.split('\n')) {
const [hashSuffix, count] = line.split(':');
if (hashSuffix.toLowerCase() = suffix.toLowerCase()) {
return parseInt(count); // number of leaks
}
}
return 0; // not found
}
HIBP performance data :
6.3 Interpretation of Count HIBP
The count returned by HiBP indicates the number of times this exact password appeared in indexed leaks:
CountInterpretationCrack time (MD5, 12× RTX 4090) ------------------------------------------------------------ > 1 000 000Top 100 World< 1 nanosecond 10,000–1,000,000Very common< 10 nanoseconds 1 000–10 000Common< 100 nanoseconds 100–1,000Uncommon but known< 1 microsecond 1–100Rare but compromised< 10 microsecondsEven a count of 1 (the password appeared only once in a leak) indicates a vulnerability: if an attacker targets this account specifically and has the list of credentials of the leak in question, he will find it.
7. Time2Crack Language Wordlists
7.1 Why language wordlists in addition to HIBP
HiBP covers the exact compromise credentials — but not the common words never run away yet. A francophone user who chooses "ephemeral" as a password may not be in HiBP (the word is unlikely in anglophone credentials), but it is in the French wordlist of any attacker targeting a francophone system.
Time2Crack's language wordlists cover this gap:
LanguageSourceEstimated size -------------------------------- EnglishSecLists Wikipedia EN~200,000 entries French (fr)SecLists Wikipedia EN~150,000 entries SpanishSecLists Wikipedia ES~150,000 entries Portuguese (pt)SecLists Wikipedia PT~120,000 entries GermanSecLists Wikipedia DE~180,000 entries Turkish (tr)SecLists Wikipedia TR~100,000 entries Italian (it)kkrypt0nn~100,000 entries Polish (pl)kkrypt0nn~80 000 entries Netherlands (nl)kkrypt0nn~80 000 entries Filtering applied Only words of ≥ 4 characters are retained (the 1–3 characters are too short to be a realistic password, and their inclusion would inflate the list without added value).7.2 Lazy loading
Wordlists are not included in the code — they are loaded on request when changing language, to meet the network budget:
// app.js - loadDictionary()
async function loadDictionary(lang) {
if (DICTlang & DICTWORDS) return; // Déjà chargé
if (DICTLOADING) {
DICTPENDINGLANG = lang; // Waiting file
return;
}
DICTLOADING = true;
const res = await fetch(data/wordlists/${lang}.txt);
const text = await res.text();
// Convert to Set<string> for O(1) lookup
DICTWORDS = new Set(
text.split("\n")
.map(w => w.normalize("NFC").trim().toLowerCase())
.filter(w => w.length >= 4)
);
DICTLANG = Lang;
DICTLOADING = false;
}
Why a Set ? Structure Set JavaScript guarantees O(1) lookups — regardless of dictionary size (50,000 or 200,000 entries), verification DICTWORDS.has(word) takes the same constant time. Array would require O(n) per lookup, i.e. up to 200,000 comparisons per check.
Loading queue
The DICTPENDINGLANG avoids race conditions during rapid language changes:
Scénario : l'utilisateur switche rapidement EN → FR → DE
├── EN demandé : DICTLOADING = false → start of the fetch EN
FR requested during fetch EN : DICTLOADING = true → DICTPENDINGLANG = "fr"
├── DE demandé pendant fetch EN : DICTLOADING = true → DICTPENDINGLANG = "de" (crash "fr")
- Fetch Ends: DICTLANG = "en", puis lance loadDictionary("de")
└── Résultat : seule la dernière langue demandée est chargée
This behaviour is intentional: the most recently requested language is loaded, not all intermediate languages.
8. Dictionary Detection in Time2Crack: isDictWord()
8.1. Detection operation
The function isDictWord(pw) determines whether a password is or is derived from a current dictionary word:
// app.js - ligne 2181
function isDictWord(pw) {
if (!DICT(WORDS) return false;
// Standardization: NFC + Lowercase
const l = pw.normalize("NFC").toLowerCase();
// Direct verification
if (DICT)
WORDS.has(l) || DICTWORDS.has(deLeet(pw)) return true;
// Morphological variations
const deleetWord = deLeet(pw);
const variations = getMorphVariations(deleetWord);
for (const variation of variations) {
if (DICT)
WORDS.has(variation)) return true;
}
return false;
}
3 layer detection line :
8.2 Unicode Standardization (NFC)
Standardization normalize("NFC") ensures that accented characters are treated consistently:
This standardization is applied both when creating the dictionary and when checking — ensuring correct correspondence for accent languages (FR, ES, DE, PT, etc.).
8.3 Impact on vulnerability detected
When isDictWord() Go back trueseveral attacks become applicable:
hybridVuln = true → 1000 mutations tested on dictionary basis9. De-leetification: how the attacker decodes substitutions
9.1 Leetspeak and its limitations as protection
The leetspeak (or "1337 speech") is a practice of replacing letters with visually similar numbers or symbols: a→@, e→3, i→1 or !, o→0, s→$. Users use it to "strengthen" common words.
The reality: these substitutions are the first rules applied in any dictionary attack with rules of mutation. They do not constitute a real defense against a competent attacker.
9.2 Implementation of deLeet() in Time2Crack
// app.js - ligne 3622
const LEETBASE = {
a: ["@", "4"],
e: ["3"],
o: ["0"],
s: ["$", "5"],
t: ["+", "7"],
h: ["#"],
g: ["9"],
};
function ofLeetWith(pw, oneMap) {
let r = pw.normalize("NFC").toLowerCase();
for (const [ch, reps] of Object.entries(LEET)
BASE))
for (const c of reps) r = r.split(c).join(ch);
// Appliquer la résolution ambiguë (i ou l) en dernier
for (const [ch, reps] of Object.entries(oneMap))
for (const c of reps) r = r.split(c).join(ch);
return r;
}
function ofLeet(pw) {
// Ambiguity: "1" may be "i" or "l"
const withI = deLeetWith(pw, { i: "1!", l: "" });
const withL = deLeetWith(pw, { l: "1", i: "!" });
// If the dictionary is loaded, prefer the variant that matches
if (DICT)WORDS) {
if (DICT)WORDS.has(withL)) return withL;
if (DICT)WORDS.has(withI) return withI;
}
// Otherwise: prefer the variant with the least remaining numbers
const digestsI = (withI.match(/\d/g)
const digestsL = (withL.match(/\d/g)
return digit L <= digit I ? withL: withI;
}
9.3 Ambiguity Management "1" → "i" or "l"
The substitution "1" is ambiguous: "1" can represent "i" (as in "1nfo" → "info") or "l" (as in "p1ayer" → "player"). Time2Crack resolves this ambiguity by:
9.4 Substitutions not covered (intentionally)
Time2Crack does not cover rare or ambiguous substitutions beyond LEETBASE patterns. It is a design choice: covering too many cases would increase the false positives (detecting a password as a "current word" while it is not). The included patterns cover >95% of the actual leetifications observed in the corpus.
10. Calculation of cracking time: addDictionaryAttacks()
Calculation logic
// app.js - ligne 3848
function addDictionaryAttacks(rows, common, weak, dictWord) {
for (const a of ALGOS) {
let sec;
if (weak) sec = 100 / a.rate; // Top ~100 entrées
else if (common) sec = 10000 / a.rate; // HIBP priority list ~10k
else if (dictWord) sec = 200000 / a.rate; // Scan wordlist complète ~200k mots
else sec = null; // Non applicable
const note = weak ? t("nWeakPassword")
: common ? t("nInLeaks")
: dictWord ? t(nDictHit)
: t(nAbsentLeaks);
rows.push({ atk: t("aDict"), hash: a.name, spleen: a.rate, sec, note, cat: "dict" });
}
}
10.2 Three levels of vulnerability
Level 1 — Ultra-weak (weak = true) : ~100 / spleen
The password is in the world's Top 100 ("password", "123456", "qwerty"...). An attacker testing these 100 priority candidates finds it in the first pass. For MD5: 100 / 168.9e9 0.6 nanosecond.
Level 2 — Common (common = true) : ~10 000 / spleen
The password is in the reference HIBP list of the ~10,000 most frequent passwords. These candidates are tested in the first microseconds. For MD5: 10,000 / 168,9e9 59 nanoseconds.
Level 3 — DictWord (dictWord = true) : ~200,000 / spleen
The password is a word from the current language dictionary (local wordlist ~50k–200k entries). A complete scan of the wordlist is almost instantaneous for fast algorithms. For MD5: 200 000 / 168,9e9 1.18 microsecond.
Level 4 — Not applicable (sec = null) : the password is neither ultra-weak, nor common, nor a word from the current dictionary. The dictionary attack alone is not applicable — other attacks (gross force, Markov, PCFG) become the main threat.
10.3 Why these numbers (100, 10,000, 200,000)?
These values are calibrated to actual data:
10.4 Complete numerical example
For the "sun" password (current French word, detected as dictWord) :
Even for Argon2id, 250 seconds is less than 5 minutes — for a simple word from the French dictionary.
11. High fidelity calibration: applyHighFidelityCalibration()
11.1 Why additional calibration?
The gross calculation addDictionaryAttacks() gives an estimate of the "worst case" — as if the attacker was testing the entire wordlist uniformly. ordered by probability : the most common passwords arrive first.
High fidelity calibration applies a corrective multiplier based on empirical observations:
// app.js - ligne 4442
case "dict":
// Ur et al. 2012 : mots communs trouvés dans les 100-10k premiers essais
// Weir 2009 : mots dict trouvés dans les 200k premiers essais (scan ordonné)
m = context.common ? 0.15: context.dictWord ? 0.5: 1.0;
Break;
Multiplier common (0.15) : if the password is in the common priority list, it is found on average at the 15th percentile of the list — that is ~1,500 tests of the 10,000 listed. Time is multiplied by 0.15 → 6.7× faster than the uniform scan.
Multiplier dictWord (0.5) : a common dictionary word is found on average mid-term of a well-ordered list. Multiplier 0.5 → 2× faster than the uniform scan.
11.2 Academic Sources of Calibration
12. Likelihood of applicability and confidence score
12.1 Scoring system
Time2Crack does not just calculate a time — it also evaluates the probability that the dictionary attack is actually the fastest attack, via a multi-factor scoring system:
// Score d'applicabilité (0-1)
case "dict":
return context.common ? 0.99 : context.dictWord ? 0.85 : 0;
common The password is in HIBP → the dictionary attack applies with maximum confidence.dictWord Only: high probability. A dictionary word is vulnerable, but an attacker must have the right linguistic wordlist.12.2 Evidence score
In parallel with the applicability score, a "evidence" score records the observed signals:
case "dict":
return Number(!!context.common) + Number(!!context.dictWord);
// 0 : aucun signal
// 1 : un signal (common OU dictWord)
// 2 : deux signaux (common ET dictWord)
An evidence score of 2 (word in both HIBP and the linguistic dictionary) boosts the final confidence of 0.08 (2 × 0.04).
12.3 Final Confidence Score
const base = confidenceBase["dict"] || 0.6; // confiance de base
const evidenceBoost = Math.min(0.12, row.evidenceScore 0.04);
const speculativePenalty = speculativeCats.has("dict") ? 0.08 : 0; // dict n'est pas spéculatif
row.confidence = Math.max(0, Math.min(1,
Math.max(applicability, base) + evidenceBoost - specificPenalty
));
For a password common with dictWord also detected:
applicability = 0.99evidenceBoost = min(0.12, 2 × 0.04) = 0.08confidence = min(1, max(0.99, 0.6) + 0.08) = min(1, 1.07) = 1.0Maximum confidence: The dictionary attack is the primary attack unambiguously.
13. Bloom filter RockYou: local detection of widespread passwords
13.1 Background
In addition to the HIBP verification (which requires a network call), Time2Crack incorporates a bloom filter A bloom filter is a probabilistic data structure that allows you to test belonging to a set with a probability of false positives (~1%), without storing the elements themselves.
13.2 Benefits of the bloom filter
CriteriaHIBP k- anonymityBloom filter local --------------------------------------------- Coverage~14 billion~14 million (RockYou) Latency50–200 ms (network)<1 ms (local) Confidentialityk- anonymity (5 SHA-1 tanks)100% local, nothing transmitted False positive0%~1% SizeAPI~2–5 MB (bloom filter binary)13.3 Operation of double hashing
// app.js - ligne 1962
function bloomHas(filter, word) {
if (!filter) return false;
const { bitArray, m, k } = filter;
const w = word.toLowerCase();
// Double-hashing: h1 and h2 are two independent hash functions
const h1 = fnv1a32(w, 2166136261); // FNV1a with standard seed
const h2 = fnv1a32(w, 0x811c9dc5); // FNV1a with alternative seed
for (let i = 0; i < k; i++) {
const pos = (h1 + (i (h2) >>> (0)) % m; // k positions in the bit array
const byteIdx = Math.floor(pos / 8);
const bitIdx = pos % 8;
// If a bit is 0: the word is DEFINITLY absent
if ((bitArray[byteIdx] & (1 << bitIdx)) = 0) return false;
}
// All bits are at 1 : the word is PROBABLY present (with 1% of FP)
return true;
}
The FNV1a double-hashing ensures a uniform distribution of positions in the bit array, minimizing collisions and keeping the false positives rate close to 1%.
13.4 Loading and binary format
The bloom filter is stored in data/rockyou.bloom — a binary file with:
The load is triggered manually by the user (button in the UI), not automatically at the page loading — to preserve the initial performance.
14. Comparison with derived attacks
The "pure" dictionary attack is the first of a family of attacks that all share a common root: a lexical wordlist. Time2Crack implements 7 offline cracking models (gross force, dictionary, hybrid, mask, PCFG, Markov, combinator), each attacking the password from a different angle.
14.1 Dictionary vs Hybrid
CriteriaDictionaryHybrid (dict+rules) ---------------------------------------------- CandidatesDictionary Words asWords + ~1000 mutations per word Keyspace200,000 candidates~200 million candidates Cracked examples"password", "sun""P@ssw0rd!", "s0l3il2024" Relative speed1000× fasterMuch wider coverage Applicable ifexact word in listPassword derived from a dict wordThe hybrid attack begins where the pure dictionary stops: it takes every word from the wordlist and applies systematic mutation rules (hashcat "best64.rule" in mind).
14.2 Dictionary vs PCFG
CriteriaDictionaryPCFG ------------------------------ ModelFixed list of candidatesProbabilistic structure grammar ForceAccurate wordsWord+Digits structures, Cap+lower+sym Example"password" → ✓"Password123" → ✓ (structure detected) CoverageKnown vocabularyGeneric human structuresA password like "Password123" can go unnoticed in a dictionary (the exact word "Password123" may not be listed) but will be cracked very quickly by PCFG which recognizes the structure [Majuscule][minuscules][digits].
14.3 Dictionary vs Markov
CriteriaDictionaryMarkov -------------------------------- CandidatesPre-compiled listFly-generated by statistical model CoverageKnown vocabularyAny probable "human" sequence AdvantageVery fast on exact wordsCovers unknown vocabulary words Example"sunshine" → ✓"sunsh1ne" potentially ✓Markov covers cases where the password looks like a human word without being in any list.
14.4 Dictionary vs Credential Stuffing
CriteriaDictionaryCredential Stuffing --------------------------------------------- ObjectiveCracking a hashTest a specific login MechanismHash local computationRemote connection attempts SourceGeneric WordlistPair login/password leaks DefenceStrong hash algorithmRate limiting, MFA, IP blockingCredential stuffing does not attack a hash — it directly tries login/password pairs on remote services. It is conceptually different from cracking, but shares the same data source (HIBP/fail).
15. Limitations of attack dictionary
15.1 Intrinsic limits
1. Known vocabulary only A invented password that does not resemble any existing word and is not in HiBP is impervious to pure dictionary attack. "Xqz7mK9pL" will never be in any wordlist. 2. Language coverage An attacker targeting a French-speaking user without a French wordlist will miss words such as "ephemeral", "grenouille", or "bricolage" that are not in standard English wordlists. 3. New passwords A credential that has just been created and has never fled will not be in HiBP — even if it is very common, it will have to wait until the next leak to find it. 4. Very long passwords A password consisting of a long invented phrase ("MaGrandeAventure2024SousLesEtoiles") will be in no wordlist — even if each word taken separately is trivial. This is the idea behind the passphrases, treated by other attacks (Combinator, PRINCE).15.2 What Time2Crack's Calculation Does Not Model
Context Wordlists : an attacker targeting a specific sector will build adapted wordlists (medical terms for hospitals, video game slang for gaming platforms). Time2Crack uses generic wordlists. Combined gross force Wordlists : Tools such as CeWL (Custom Word List Generator) can extract terms from the target website to build a custom wordlist. This level of targeting is not modelled. Recent leaks not yet in HIBP between the moment a leak occurs and its integration into HIBP, the compromised credentials circulate in private forums without being accessible via the public API. Hash algorithms not covered Time2Crack models 6 algorithms (MD5, SHA-1, SHA-256, NTLM, bcrypt, Argon2id). Algorithms such as SHA-512, scrypt, or PBKDF2 have different profiles.16. Effective defences
16.1 What protects against dictionary attack
Modern hash algorithm (bcrypt, Argon2id) This is the most effective defense on the server side.The difference of 10+ orders of magnitude between MD5 and Argon2id makes the dictionary password "sun" immune in practice if protected by Argon2id — even if the attacker knows it is a French word.
Single salt per account A salt prevents pre-calculated table attacks and forces the attacker to recalculate individually for each account. It does not protect targeted wordlist attacks (the attacker can still test the 200,000 words with the salt), but invalidates the rainbow tables and makes massive attacks on large databases impracticable. Password length A password of a single dictionary word (8-10 characters) is vulnerable. A passphrase of 4-5 words ("cheval-lumière-forest-voyage") is resistant to the pure dictionary attack but not to the Combinator attack — hence the importance of the length AND the randomity. Randomly generated passwords The real solution: use a password manager that generates long random passwords (20+ characters of all types). These passwords will never be in any dictionary. Multifactor authentication (MFA) The MFA does not prevent crack offline (the attacker recovers hashs and cracks offline), but protects against credential stuffing (use cracked credentials to connect). Rate limiting and detecting anomalies Protects against online attacks (direct connection attempts), not against offline cracking of stolen hashs.16.2 What does not protect
Simple leet substitutions : "password" → "p@ssw0rd" — see section 9. Added numbers at the end : "password" → "password1" — among the first rules tested. Initial payment : "password" → "Password" — standard capitalization rule. Figures and symbols if the word remains recognizable "sunshine!" is in any wordlist that includes "sunshine" with basic rules. Complexity imposed without length "P@s1w" meets the complexity criteria (maj, min, number, symbol) but is shorter and more predictable than "correcthorsebaterystaple".16.3 Practical recommendations
17. References
Primary sources (academic studies)
Morris, R., & Thompson, K. (1979). Password security: A case history. Communications of the ACM, 22(11), 594–597.common ? 0.15 (top 10k level)Industrial sources
Have I Been Pwned. (2024). Pwned Passwords. https://haveibeenpwned.com/PasswordsSecondary sources (context)
Troy Hunt. (2013). Introduction 306 Million Freely Downloadable Pwned Passwords. troyhunt.com.Web sources cited in the Time2Crack application
IEEE Xplore (dictional reference). https://ieeexplore.ieee.org/document/6234435descDict (app.js) for the order of coverage of top dictionaries.Document generated for Time2Crack Project — Version 1.0 — 2026-04-01 Source code:
app.js (functions addDictionaryAttacks, isDictWord, deLeet, loadDictionary, applyHighFidelityCalibration, bloomHas)*