Everything about passwords

Published: at 12:00 AM

Everything About Passwords

TL;DR

Most password policies are backwards and make things less secure. I’ll show you the math behind why fishwithahat beats ZU$Xgb8, explain how passwords actually get hacked (spoiler: it’s not brute force), and give you practical policies that don’t suck.

Prerequisites:

  • None - suitable for all technical levels
  • Basic understanding of authentication concepts helpful but not required

What you’ll learn:

  • Password entropy calculations and why length beats complexity
  • How passwords actually get compromised (not brute force)
  • Why forced password changes make security worse
  • NIST-compliant policies that actually work
  • Practical implementation strategies for organizations

Introduction: Are Most Password Policies Just Bad?

If you’ve worked in IT or security for more than five minutes, you’ve probably encountered password policies that make you wonder if they were designed by someone who’s never actually had to use a computer.

How many of these have you encountered?

  • Change password every 30-90 days
  • Password must be 8-12 characters (with arbitrary upper limits like 20-32 chars)
  • Must have uppercase, lowercase, numbers, special characters
  • Password sharing for shared SaaS accounts
  • Password reuse across multiple accounts (everyone does this)
  • Sending passwords through Slack, Teams, email, or sticky notes

Here’s the thing: most of these policies were created by people who’ve never had to actually implement secure authentication systems or deal with the business reality of user behavior. They read outdated guidelines, copy-paste from compliance checklists, and call it “security.”

The result? Overly complex rules that make passwords weaker while frustrating users.

⚠️ Warning

Security teams often implement policies that force users into unsafe workarounds.

Meanwhile, while companies force users to jump through these hoops, the actual attack vectors that compromise passwords - credential stuffing, database breaches, social engineering - have nothing to do with whether your password has a number in it.

This blog covers everything about passwords - the mathematics of password strength, how passwords are actually attacked in practice, and why most “enterprise security” recommendations ignore both technical reality and human behavior.

Let’s start with something many security professionals get wrong: what actually makes a password strong.


The Mathematics of Password Security

Let’s start with a short quiz.

⚠️ Which Password is More Secure?
  1. ZU$Xgb8
  2. fishwithahat

Think about it for a moment before expanding the answer below…

In order to answer why that is the case, I need to cover some basics like entropy, complexity and real attack calculations as well as forms of attacks.

ℹ️ But for those who want the TLDR:

Longer passwords have higher entropy, which makes them mathematically more secure.

Understanding Entropy vs Complexity

Although Password entropy and complexity sound similar, they are not the same thing.

💡 Password Complexity

Complexity usually refers to the range of characters used in a password.

From our earlier examples:

  1. ZU$Xgb8
  2. fishwithahat

ZU$Xgb8 is more complex as it uses additional characters such as $ and capitalized letters.

At the same time fishwithahat is less complex since it uses only lower case alphabet.

So the example with higher complexity is less secure? How can that be? That’s where entropy comes into place.

Password Entropy Calculations

💡 Password Entropy

Entropy is a mathematical measure expressed in bits of how unpredictable or difficult it is to guess or crack a password. It quantifies the password strength assuming each character is chosen randomly and independently from a set of symbols.

The entropy HH of a password can be calculated using the following formula:

H=L×log2NH = L \times \log_2 N

where:

  • LL = length of the password (number of characters)
  • NN = number of possible symbols for each character (the size of the character set)
  • log2Nlog_2 N = number of bits of entropy per character

The result HH is the total entropy in bits.

Now that we established what entropy is and a way to calculate we can see that increasing either the password length or the size of the character set increases the entropy and thus the password strength.

Password Entropy Calculation

H=L×log2NH = L \times \log_2 N

Where:

  • LL = length of the password
  • NN = number of possible symbols for each character

1. ZU$Xgb8 (Complex Password)

Analysis:

  • Length LL: 7 characters
  • Character sets used:
    • Uppercase letters: 26
    • Lowercase letters: 26
    • Digits: 10
    • Special characters: 32 (common symbols)
  • Total symbols NN = 94

Calculation:

H=7×log2947×6.554645.88 bitsH = 7 \times \log_2 94 \approx 7 \times 6.5546 \approx \mathbf{45.88 \text{ bits}}

2. fishwithahat (Simple Password)

Analysis:

  • Length LL: 12 characters
  • Character sets used:
    • Lowercase letters only: 26
  • Total symbols NN = 26

Calculation:

H=12×log22612×4.700456.40 bitsH = 12 \times \log_2 26 \approx 12 \times 4.7004 \approx \mathbf{56.40 \text{ bits}}

Key Insight: Despite appearing “weaker,” the longer password fishwithahat has 23% more entropy (56.4 vs 45.9 bits) than the “complex” shorter password.

ℹ️ Conclusion: length vs Complexity

This above calculation demonstrates why length often trumps complexity in password security.

Why Complexity Rules Are Backwards

Understanding why complexity rules fail is only part of the story. Effective defenses require proper examination of how attackers actually target passwords in practise.

Most password policies force this pattern:

  • At least 1 uppercase letter
  • At least 1 lowercase letter
  • At least 1 number
  • At least 1 special character
  • Minimum 8 characters

The Issue: This dramatically reduces the actual search space because users follow predictable patterns.

Here are 2 examples of how real users behave in a situation where there are two policies.

  • Policy A: Forced Complexity (8 chars)
  • Policy B: Forced Length (12 chars)
AspectExample A: Forced Complexity (8 chars)Example B: Simple but Long (12 chars)
Typical ResultPassword1!correcthorsebattery
Policy Assumption9586.695^8 \approx 6.6 quadrillion combinations
(all ASCII printable characters)
26129526^{12} \approx 95 trillion combinations
(lowercase letters only)
User Behavior RealityPattern: [Capital] [lowercase word] [number] [symbol]Pattern: Multiple dictionary words
Actual Components2,000\approx 2{,}000 common words
1010 possible numbers (09)(0{-}9)
5\approx 5 common end symbols !,@,#,$,%
2,000\approx 2{,}000 common words per position
343{-}4 word combinations
• No forced patterns
Real Search Space2,000×10×52{,}000 \times 10 \times 5 \approx 100 thousand combinations2,00032{,}000^3 \approx 8 billion combinations
Security RealityWeaker - Highly predictable patternStronger - Less predictable, more entropy
Security RealityWeaker - Entropy gap: log2(105)17\log_2(10^5) \approx 17 bits - Highly predictable patternStronger - Entropy gap: log2(8×109)33\log_2(8 \times 10^9) \approx 33 bits - Less predictable
Crack Time EstimateMinutes with modern GPUDays to weeks with modern GPU
💡 Benefits for everyone

Not only Policy B is better in terms of security, it’s also better for the end user as it’s much simpler to remember and use a password like correcthorsebattery

Now that we’ve established why length matters more than complexity, let’s look at how attackers actually exploit the gap between theoretical and practical password strength.

How Passwords Are Actually Attacked

These attack methods highlight why proper password storage is critical. Appropriate storage controls can be the difference between minor incident and a catastrophic breach.

Attack Method Breakdown

There are many attack vectors that can compromise a password and here I try to cover most relevant ones. Notice how most “security advice” focuses on defending against the least likely attacks while ignoring the methods that actually work.

Each attack method below includes:

  • Description: What the attack actually does
  • Speed: How fast it works in practice
  • Prerequisites: What the attacker needs
  • Defense: How to actually protect against it
  • Real-World Usage: How common it actually is (the part that matters most)
⚠️ Reality Check

These attack times assume the attacker knows which pattern category your password uses. In practice, modern cracking tools test all common patterns simultaneously, making these times even faster.

The Reality of Offline vs Online Attacks

Regardless of how many attack techniques there are on passwords, reality is that most if not all attacks don’t actually involve “guessing” or cracking these days. It’s far more common and actually much easier for attackers to use social engineering or other techniques to get a hold of your password.

Online vs Offline: The Critical Distinction

Guessing or cracking requires the following:

  • Compute power
  • Energy
  • Time

Compute and energy have significant impact on the overall costs of attack, often making it inefficient or impractical in real life scenarios. However time is something that can be limited further, almost eliminating the impact of online only guessing attacks. This does not apply to offline attacks described below.

💡 Rate Limiting

A very common and standard password security practice is rate limiting which essentially makes online brute force attacks impractical.

Rate limiting is simply limiting the number of guesses a user can do over a given period of time.

It’s simple yet effective against online attacks as it prevents “infinite” guessing.

A very well known rate limiting technique is CAPTCHA

Rate limiting often implements exponential backoff to keep increasing the time between guesses, eventually growing so large that it can take months or years between guesses. This completely eliminates guessing as no attacker will wait days or months between each guess.

Unfortunately this is not the case for offline attacks.

Offline Attacks

Rate limiting only affects online password attacks, if an attacker was able to acquire password hashes or any other form of passwords, they are then able to brute-force it as rate limiting no longer helps.

Being able to offline attack passwords completely changes the risk profile. An attacker then has theoretically unlimited time to keep guessing passwords as the only limitation they have is compute power and energy.

ℹ️ Common Password lists

Did you know that there are readily available lists containing millions upon millions of passwords and hashes?

Check out:

  • rockyou.txt (Many versions of this exists that can be found on the web. e.g RockYou2024 contains nearly 10 billion passwords)
  • Have I Been Pwned (Can check if email/password been found in known breaches)
  • SecLists Common Credentials (curated list of lists)
  • WeakPass (Repository containing many lists, variations and subsets of other lists)

When Attackers Get Your Database

While online guessing is uncommon and most often ineffective, offline cracking is a completely different ball game.

ℹ️ Most passwords are stolen, not guessed!

The truth is that most passwords are stolen from databases, chats, text files etc.

Blind guessing or cracking is not used commonly as there are simpler and far more effective attacks.

💡 The importance of Password storage policy

Password storage policy is arguably more important than password complexity or/and length.

Without appropriate storage controls, offline password attacks can completly bypass things like:

  • Rate limiting
  • Password complexity
  • Password length

Compliance and Regulatory Requirements

We can’t talk about passwords without mentioning their storage. This topic is far too broad to be discussed in detail but the importance of it must be stated.

Let’s briefly look at some common compliance requirements from GDPR, PCI-DSS and enterprise standards like NIST, ISO and SOC.

⚠️ Compliance Reality Check

Having a compliance checklist doesn’t equal security. Many organizations meet compliance requirements on paper while using MD5 hashes or storing passwords in Excel files.

The goal should be actual security, with compliance as a beneficial side effect.

ℹ️ Implementation Priority

If you’re starting from scratch:

  1. Fix the technical implementation first (proper hashing, salting, secure storage)
  2. Document your processes (policies, procedures, incident response)
  3. Implement monitoring and logging (audit trails, breach detection)
  4. Regular reviews and updates (compliance assessments, policy updates)

Getting the basics right protects you better than any compliance framework.

Password Storage: The Technical Foundation

What is Hashing?

Cryptographic Hash Function Definition

H:{0,1}{0,1}nH: \{0,1\}^* \rightarrow \{0,1\}^n

Where:

  • HH = cryptographic hash function
  • {0,1}\{0,1\}^* = set of all finite binary strings (domain)
  • {0,1}n\{0,1\}^n = set of all binary strings of length nn (codomain)
  • nn = output length in bits (digest size)

Computational Properties

1. Deterministic:

x{0,1}:H(x) produces identical output\forall x \in \{0,1\}^*: H(x) \text{ produces identical output}

2. Efficient Computation:

H(x) computable in O(x) timeH(x) \text{ computable in } \mathcal{O}(|x|) \text{ time}

3. Uniform Distribution:

y{0,1}n:Pr[H(x)=y]=2n for uniform random x\forall y \in \{0,1\}^n: \Pr[H(x) = y] = 2^{-n} \text{ for uniform random } x

Security Properties

4. Preimage Resistance (One-wayness):

h{0,1}n:finding x such that H(x)=h requires Ω(2n) operations\forall h \in \{0,1\}^n: \text{finding } x \text{ such that } H(x) = h \text{ requires } \Omega(2^n) \text{ operations}

5. Second Preimage Resistance:

x1:finding x2x1 such that H(x1)=H(x2) requires Ω(2n) operations\forall x_1: \text{finding } x_2 \neq x_1 \text{ such that } H(x_1) = H(x_2) \text{ requires } \Omega(2^n) \text{ operations}

6. Collision Resistance:

finding distinct x1,x2 such that H(x1)=H(x2) requires Ω(2n/2) operations\text{finding distinct } x_1, x_2 \text{ such that } H(x_1) = H(x_2) \text{ requires } \Omega(2^{n/2}) \text{ operations}

Avalanche Effect

For single-bit input changes, approximately half the output bits should flip:

x1,x2{0,1}:dH(x1,x2)=1    E[dH(H(x1),H(x2))]n2\forall x_1, x_2 \in \{0,1\}^*: d_H(x_1, x_2) = 1 \implies \mathbb{E}[d_H(H(x_1), H(x_2))] \approx \frac{n}{2}

Where:

  • dH(a,b)=i=1a[aibi]d_H(a,b) = \sum_{i=1}^{|a|} [a_i \neq b_i] is the Hamming distance
  • E[]\mathbb{E}[\cdot] denotes expected value

Complexity Analysis

Lower Bounds:

  • Collision attacks: Birthday bound gives Ω(2n/2)\Omega(2^{n/2}) complexity
  • Preimage attacks: Information-theoretic bound gives Ω(2n)\Omega(2^n) complexity
  • Generic security: These bounds are optimal for ideal hash functions

Practical Instantiations: SHA-256 (n=256n = 256), SHA-3 (n{224,256,384,512}n \in \{224, 256, 384, 512\})


A cryptographic hash function establishes a computationally irreversible mapping from variable-length inputs to fixed-length outputs, where finding preimages or collisions requires exponential effort despite polynomial-time forward computation.

Here is a visual diagram to help explain the maths overload from earlier. We again use fishwithahat as the input with a common hashing algorithm SHA-256

Note how the same input always produces the same output and a single change within input, results in drastic change of the output.

flowchart TD
    A["fishwithahat"] --> B[SHA-256]
    A2["fishwithahat"] --> B2[SHA-256]
    B --> C["7a8b9c1d2e3f45...6g7h
    🔒 Always identical
    (Deterministic property)"]
    B2 --> C2["7a8b9c1d2e3f45...6g7h
    🔒 Always identical
    (Deterministic property)"]

    F["fishwithbhat
    (5th letter: a→b)"] --> G[SHA-256]
    G --> I["3x4z8m7n9k5j2l...5w8q
    🌊 ~50% bits flipped
    (Avalanche criterion)"]

    classDef inputChanged fill:#854d0e,stroke:#fbbf24,stroke-width:2px
    classDef outputDifferent fill:#9a3412,stroke:#fb923c,stroke-width:2px

    class F inputChanged
    class I outputDifferent

We can more generically visualise this process as inputing any digital data through a hash function to produce a fixed length output.

flowchart TD
    A["Any Size Input
    📄 'hello world'
    📊 1GB file
    🔢 123456789"] --> B["Hash Function
    SHA-256"]
    B --> C["Fixed Size Output
    256-bit digest
    64 hex characters"]
💡 Hashing in plaintext

Hashing is essentially mapping any sized data to fixed sized outputs while retaining the security and computational properties mentioned earlier.

  1. Same input always gives same output

  2. Tiny changes create completely different outputs - Change one letter in “fishwithahat” and roughly half the hash bits flip, making it look totally unrelated.

  3. Easy to compute forward, impossible backward - You can quickly hash any data, but given just the hash, finding the original data would be mathematically infeasible

  4. Finding two different inputs with the same hash is extremely hard - Even with massive computing power, creating hash collisions is computationally infeasible.

Think of it as a one-way mathematical blender that turns any ingredient into a unique, fixed-size smoothie that can’t be unmixed.

What is Salting?

Salt-Enhanced Hash Function Definition

Hsalt:{0,1}×{0,1}s{0,1}nH_{salt}: \{0,1\}^* \times \{0,1\}^s \rightarrow \{0,1\}^nHsalt(m,salt)=H(saltm) or H(msalt)H_{salt}(m, salt) = H(salt \| m) \text{ or } H(m \| salt)

Where:

  • HsaltH_{salt} = salted hash function
  • m{0,1}m \in \{0,1\}^* = message/password to be hashed
  • salt{0,1}ssalt \in \{0,1\}^s = random salt value of length ss bits
  • \| = concatenation operator
  • HH = underlying cryptographic hash function

Security Enhancement

Rainbow Table Resistance:

m:precomputed H(m) values become useless against H(saltm)\forall m: \text{precomputed } H(m) \text{ values become useless against } H(salt \| m)

Unique Hash Property:

m,salt1,salt2:salt1salt2    H(salt1m)H(salt2m)\forall m, salt_1, salt_2: salt_1 \neq salt_2 \implies H(salt_1 \| m) \neq H(salt_2 \| m)

Dictionary Attack Mitigation:

Attack complexity increases from O(D) to O(D×2s)\text{Attack complexity increases from } \mathcal{O}(|D|) \text{ to } \mathcal{O}(|D| \times 2^s)

Where:

  • D|D| = size of password dictionary
  • ss = salt length in bits

Salt Requirements

Uniqueness: Each password should use a cryptographically random salt Length: Minimum 128 bits (s128s \geq 128) for adequate security Storage: Salt must be stored alongside the hash (not secret)


Salting transforms deterministic hash functions into probabilistic constructions, ensuring identical inputs produce different outputs when paired with unique salts, thereby defeating precomputed attack vectors.

These salt values are often stored together with that hash of the password on a table somewhere in a database and looks something like this:

user_idsaltpassword_hash
1x7K9mP2qa1b2c3d4e5f6…
2n4R8sL1w9z8y7x6w5v4u…
3m8T5pL9r7m4k8n2p3q1s…

Here’s how salting works in practice using our earlier example fishwithahat

flowchart TD
    A["Password: fishwithahat"] --> D[Combine]
    B[Generate Random Salt] --> C["Salt: x7k9m2n4p8q1"]
    C --> D
    D --> E["fishwithahat + x7k9m2n4p8q1"]
    E --> F[SHA-256]
    F --> G["Salted Hash:
    9f2a8b3c7d4e5f..."]

    classDef saltHighlight fill:#0c4a6e,stroke:#38bdf8,stroke-width:2px

    class C saltHighlight

The same password with different salts produces completely different hashes:

flowchart TD
    A["fishwithahat"] --> C[Combine with Salt 1]
    B["Salt 1: a1b2c3d4"] --> C
    C --> D[Hash Function]
    D --> E["Hash 1:
    3f7a9b2c4d..."]

    A2["fishwithahat"] --> C2[Combine with Salt 2]
    B2["Salt 2: x7k9m2n4"] --> C2
    C2 --> D2[Hash Function]
    D2 --> E2["Hash 2:
    8x4z7m1n5k...
    (Completely different)"]

    classDef salt1 fill:#0c4a6e,stroke:#38bdf8,stroke-width:2px
    classDef salt2 fill:#134e4a,stroke:#5eead4,stroke-width:2px
    classDef hash1 fill:#713f12,stroke:#fbbf24,stroke-width:2px
    classDef hash2 fill:#7c2d12,stroke:#fb923c,stroke-width:2px

    class B salt1
    class B2 salt2
    class E hash1
    class E2 hash2
⚠️ Why Salting Matters

Without salts, identical passwords create identical hashes. This enables attackers to use precomputed rainbow tables containing millions of common password hashes to crack passwords.

With unique salts, even identical passwords produce different hashes, making precomputed attacks impossible. Each password requires individual brute-force attempts.

Salt values only need to be unique and random to provide their security benefits. They don’t need to be secret - they’re often stored alongside hashes.

How to Salt like master-chef?

It’s quite surprising that even though doing proper password salting is relatively easy and inexpensive, it’s still widely done incorrectly.

Proper salt requires:

  • Minimum length of atleast 128 bits
  • Salt must be generated with a proper cryptographically secure random function (CSPRNG)
  • Stored separately from the password hashes (Preferred but not required)
  • MUST BE UNIQUE for each password
  • Should be applied to password before hashing
  • Optional: Addition of “pepper”
ℹ️ Add Pepper with your Salt

Often you’d also want a system/application wide “pepper” value in addition to your random salt values.

The "pepper" is a SECRET value that must be:

  • Stored separately from the database (environment variables, HSM, key vault)
  • Never logged or exposed in error messages
  • Rotated periodically with proper key versioning
  • Backed up securely with disaster recovery procedures

The stored hash can then be expressed by the following:

stored_hash=KDF(pwdsaltpepper)\text{stored\_hash} = \text{KDF}(\text{pwd} \oplus \text{salt} \oplus \text{pepper})

Where KDF stands for Key Derivation Function

Warning: If the pepper is compromised, all password hashes become vulnerable to accelerated attacks.

Below is a visual diagram showcasing the process of creating, storing and comparing hashes during a typical login process.

flowchart TD
    A[User Password: 'mypassword123'] --> B[Generate Unique Salt]
    B --> C[Salt: 'a8f5f167f44f4964e6c998dee827110c']

    D["Application-wide Pepper
    Stored in config/env"] --> E[Pepper: 'secretKey2024!']

    A --> F[Combine Components]
    C --> F
    E --> F

    F --> G["Combined String:
    'mypassword123' + 'a8f5f167f44f4964e6c998dee827110c' + 'secretKey2024!'"]

    G --> H["Hash Function
    Argon2id/bcrypt/scrypt"]

    H --> I["Final Hash:
    '$argon2id$v=19$m=65536,t=2,p=1$...$...'"]

    I --> J[Store in Database]
    C --> J

    J --> K[("Database Record:
    username: 'john'
    salt: 'a8f5f167...'
    hash: '$argon2id$v=19...'
    Note: Pepper NOT stored")]

    L[Login Attempt] --> M[Retrieve Salt from DB]
    M --> N[Get Pepper from Config]
    L --> O[User enters password]
    O --> P["Reconstruct:
    password + salt + pepper"]
    N --> P
    P --> Q[Hash with same function]
    Q --> R{Compare Hashes}
    K --> R

    R -->|Match| S[Authentication Success]
    R -->|No Match| T[Authentication Failed]

    classDef saltNode fill:#0c4a6e,stroke:#38bdf8,stroke-width:2px
    classDef pepperNode fill:#701a75,stroke:#d946ef,stroke-width:2px
    classDef hashNode fill:#713f12,stroke:#fbbf24,stroke-width:2px
    classDef dbNode fill:#1e3a8a,stroke:#60a5fa,stroke-width:2px
    classDef loginNode fill:#065f46,stroke:#34d399,stroke-width:2px
    classDef successNode fill:#14532d,stroke:#4ade80,stroke-width:2px
    classDef failNode fill:#7f1d1d,stroke:#f87171,stroke-width:2px

    class C saltNode
    class E pepperNode
    class I hashNode
    class K dbNode
    class L,M,N,O loginNode
    class S successNode
    class T failNode

Hash Storage in Practice

Now that we established what hashing and salting is, it becomes quite obvious why companies store the hash of your password and not the actual password.

When a user logins anywhere, the input password they type gets hashed and then compared to the stored hash value. If they are the same, the login attempt is approved.

This is why when password datababses get hacked, the attackers only obtain the hash values of your password and often the salt value. There are benefits to storing salt values separately from the passwords in such cases but since knowing the salt value does not change the security, it is often stored together with the password.

Why salting matters

Most common passwords and their hashes already exist in pre-computed hash tables, as such if you are not salting your passwords an attacker can almost instantly reverse most common passwords.

Modern Hashing Algorithms

Here is a quick comparison of some common modern hashing algorithms.

AlgorithmSpeedSalt SupportCost ParameterRecommended SettingRecommended
MD5Very FastManualNoneN/ANO
SHA1FastManualNoneN/ANO
SHA256FastManualNoneN/AFor non-passwords
bcryptSlowBuilt-inRounds (4-15)12-14 (250ms target)YES
scryptSlowBuilt-inN, r, p paramsN=32768, r=8, p=1YES
Argon2SlowBuilt-int, m, p paramst=3, m=64MB, p=1YES (newest)
ℹ️ Hashing usecases

In practise, hashing is used for more than just storage of passwords. Each unique usecase for hashing require different properties that benefit that usecase.

⚠️ MD5 and SHA1

You cannot use MD5 or SHA1 for passwords.

  • MD5 can be computed at over 50 billion hashes per second on a modern GPU.
  • SHA1 isn’t much better at around 20 billion per second.

These were designed for file integrity, not password security.

⚠️ Speed of hashing

Fast hashing algorithms are excellent for checksums and digital signatures where you want quick verification.

For passwords, speed is your enemy.

For example, if a bcrypt verification takes 50100ms50-100ms, that should be acceptable for a user attempting to login once. However, an attacker who has to compute millions of hashes faces a different reality:

  • User experience: 100ms100ms delay = barely noticeable
  • Attacker cost: 100ms×1,000,000100ms \times 1,000,000 attempts = 27.827.8 hours for 1 million guesses
  • Compare to MD5: Same 1 million guesses = 0.020.02 seconds

The attacker’s time increases by a factor of 5 million while user impact is negligible.

This is because cracking time can be expressed as the following:

Cracking Time=Hash Computations RequiredHashes per Second\text{Cracking Time} = \frac{\text{Hash Computations Required}}{\text{Hashes per Second}}
💡 Speed for password hashing

Specifically for password hashing, you want more resource intensive hashing algorithms to make attackers life more difficult.

Real-world cracking speeds (RTX 4090 GPU):

AlgorithmHashes/SecondTime for 1B guesses
MD550 billion20 seconds
SHA25620 billion50 seconds
bcrypt (cost 12)1,00031.7 years
Argon2500-200015-63 years

This is why “computational cost” matters more than algorithm complexity. Also specialised hardware like ASIC miners can be 100x faster for certain algorithms.

ℹ️ Cost factors and future-proofing:

Modern password hashing algorithms include adjustable “cost” parameters. As hardware gets faster, you can increase the cost factor to maintain the same level of security without changing your entire system. bcrypt uses “rounds,” scrypt uses memory and CPU parameters, Argon2 uses time, memory, and parallelism factors.

User Behavior: The Human Factor

The reality of passwords is that no matter your password policy, users tend to follow similar patterns.

Analysis of these patterns shows that most “complex” passwords used by users still fall into some form of a template:

Below is a table that tries to show some commonly used patterns that most if not all users follow to some extent. This is just some examples of patterns used, in practice there are a lot more patterns than this, some even have the appearance of “complexity” while offering no increase in security.

Pattern CategoryTemplateExample 1Example 2Example 3
Temporal[Word][Year][Symbol]Password2024!Security2025@Login2024#
[Month][Year][Symbol]January2024#March2025!December2024@
[Season][Year][Symbol]Summer2024!Winter2025@Spring2024#
[Quarter][Year][Symbol]Q12024!Q42025#Q22024@
Workplace[Company][Year][Symbol]Microsoft2024!Google2025@Apple2024#
[Department][Numbers][Symbol]Finance123!Marketing456@Sales789#
[Role][Year][Symbol]Manager2024!Developer2025@Admin2024#
[Location][Numbers][Symbol]London123!NewYork456@Paris789#
Personal[Name][BirthYear][Symbol]John1985!Sarah1992@Mike1988#
[Pet][Year][Symbol]Fluffy2024!Rex2025@Bella2024#
[Hobby][Numbers][Symbol]Football123!Guitar456@Reading789#
Substitution[L33t][Numbers][Symbol]P@ssw0rd123!S3cur1ty456@Adm1n789#
[Common_Sub][Year][Symbol]S3cur1ty2024!P@ssw0rd2025@Acc3ss2024#
Keyboard[Sequential][Numbers][Symbol]Qwerty123!Asdf456@Zxcv789#
[Pattern][Numbers][Symbol]123qwe!456rty@789uio#
[Shift_Pattern][Numbers]!QAZ2wsx@WSX3edc#EDC4rfv
Incremental[Base][Counter][Symbol]Password01!Password02!Password03!
[Word][Sequential_Year]Spring2024!Summer2024!Fall2024!
Padding[Base][Repeated_Digits]Password123!Security456!Access789!
[Base][Repeated_Symbols]Password1!!Security2@@Access3##
[Prefix][Base][Suffix]MyPassword1!TheSecret2@MyAccess3#
Industry[Domain_Word][Numbers][Symbol]Medical123!Banking456@Education789#
[Role_Word][Year][Symbol]Nurse2024!Teller2025@Teacher2024#
Why This Matters to Your Business

When your security policy forces users into these patterns, you’re not getting 95895^8 possible passwords. You’re getting maybe 100,000100,000 variations that attackers can enumerate in hours, not centuries.

Now that we’ve seen some patterns, the table below shows the real world impact these policies have on your user base and security level.

Pattern TypeComponentsSearch Space CalculationActual Combinationsvs Theoretical 8-char
[Word][Year][Symbol]2000 words × 10 years × 5 symbols2000×10×52000 \times 10 \times 5100,000100,0000.0015%0.0015\%
[Month][Year][Symbol]12 months × 10 years × 5 symbols12×10×512 \times 10 \times 56006000.000009%0.000009\%
[Company][Year][Symbol]500 companies × 10 years × 5 symbols500×10×5500 \times 10 \times 525,00025,0000.00038%0.00038\%
[Name][BirthYear][Symbol]1000 names × 80 years × 5 symbols1000×80×51000 \times 80 \times 5400,000400,0000.006%0.006\%
[L33t][Numbers][Symbol]1000 l33t words × 1000 nums × 5 symbols1000×1000×51000 \times 1000 \times 55,000,0005,000,0000.076%0.076\%
[Sequential][Numbers][Symbol]20 sequences × 1000 nums × 5 symbols20×1000×520 \times 1000 \times 5100,000100,0000.0015%0.0015\%
Average PatternCombined realistic patterns105\approx 10^5100,000\approx 100,0000.0015%\approx 0.0015\%
Theoretical 8-charAll printable ASCII95895^86.6×10156.6 \times 10^{15}100%100\%
Harsh Reality of Password policy

Your password policy assumes users will randomly select from the entire character space when the reality is that they don’t.

The Business Impact

We’ve seen that the theoretical security vs practical security are two totally different values.

While I understand that this may seem like an exaggerated or extreme example, it’s unfortunately how a large portion of users behave to some extent. I am yet to meet a single individual who has not used some form of patterns, rotations or other manipulations to their passwords. In the early 2000s, I was guilty of this myself but I did not know any better at the time.

The right password policy can not only help your security posture, it can also benefit your users by removing:

  • Useless password rotations that do more harm than good
  • Useless password complexity rules that add no measurable security

This way the user has less things to remember, they don’t have to recall unmemorable garbage of letters and symbols like Ax&2@kl2oP! and instead can remember simple sequence of words such as chickentanklettuce

Additionally, by not having to rotate these passwords every 30, 60, 90, 180 days or whatever else your policy suggests, the users are less likely to use patterns and/or rotations to their passwords.

Don’t get me wrong, I still think there is a place and time for password rotations, however the current implementations and policies are not doing it right.

💡 Key Takeaway

By simply increasing the length of the password and simplifying the complexity to an extent you can both increase the security of these passwords while simultaneously reducing the cognitive load on your users.

Most Companies Still Stuck in 2000s-Era Rules

NIST SP 800-63B (2017) changed their password guidelines and yet most policies and security experts still recommend things that are outdated and ineffective.

  1. Password expiration is DEAD

The old recommendation of forced password change every 30-90 days is no longer acceptable.

It shouldn’t be recommended at all, yet most companies still have 90 day expiration everywhere.

  1. Composition rules - Mostly DEAD

Silly complexity rules of uppercases, lowercases, numbers, symbols should be gone. Yet they are everywhere.

Another example of completely outdated and ineffective measures having widespread usage.

  1. Length over complexity

Most minimum length for password is still 8 characters, yet we’ve shown how ineffective they are in practise.

In my opinion, it is completely unacceptable to have passwords with less than 16 characters and current minimums of 8-12 are still widely accepted.

Most of my passwords start at minimum 20 characters and yet so often there are arbitrary limits imposed on passwords such as they cannot be longer than 20-24-32 etc characters.

  1. Breach Detection

It’s quite simple and easy to check whether certain passwords have been detected in previous breaches. Yet most policies have no guidelines about doing compromised password detection.

All of these points above have already been addressed by NIST in 2017, yet we are in 2025 and most of these are not adopted or even used at all.

2017 recommendation vs 2025 reality

All of these points above have already been addressed by NIST in 2017, yet we are in 2025 and most of these are not adopted or even used at all.

Worst of all, many “security experts” still give wrong advice about password security.

Password Policies That Actually Work

We’ve covered a lot of information regarding passwords and their security. We’ve also seen how ineffective or outdated most password recommendations and policies are.

Let’s end with some practical and applicable recommendations that you can use immediately.

Typical Outdated Policy
Password Requirements:
  • • 8-12 characters maximum
  • • Must contain: uppercase, lowercase, number, special character
  • • Change every 90 days
  • • Cannot reuse last 3 passwords
  • • Account locks after 3 failed attempts
My recommendation
Password Requirements:
  • • Minimum 16 characters, maximum 128
  • • Cannot be in top 1 million breached passwords
  • • Change only when compromise suspected
  • • Cannot reuse last 5 passwords
  • • MFA required for all accounts
  • • Account locks after 10 failed attempts
  • • Use of Password manager (+education)

Result: Users create strong, memorable passwords they actually keep secure

Note that I am not mentioning anything about their storage, salting or hashing since that does not affect the end user.

💡 Password managers

I also recommend to use a password manager. A good password policy combined with a password manager is the the best approach in 2025.

I discuss password managers in detail in my password managers guide.

Red Flags: When Your Security Consultant Doesn’t Know What They’re Doing

Technical Red Flags

Immediate disqualifiers:

  • They recommend SHA256, SHA1, or MD5 for password storage
  • They use “hashing” and “encryption” interchangeably
  • They suggest password rotations every 30-90 days in 2025
  • They can’t explain what a salt is or why it matters
  • They recommend 8-character minimums with “complexity rules”

Marketing buzzword red flags:

  • “Military-grade encryption” (AES is public standard, not military secret)
  • “Unhackable” or “100% secure” claims about anything
  • “AI-powered security” without explaining what the AI actually does
  • “Zero-trust” thrown around without understanding the architecture

Implementation red flags:

  • They can’t explain how their recommendations work with your existing systems
  • They quote compliance frameworks but can’t explain the technical requirements
  • They recommend tools without understanding your environment
  • They’ve never actually implemented what they’re recommending

The biggest red flag: They get defensive when you ask technical questions instead of explaining clearly.

💡 Questions to Ask Your Security Consultant

Quick competence check:

  • “What’s the difference between hashing and encryption?”
  • “Why shouldn’t we use SHA256 for passwords?”
  • “What is password entropy and how is it calculated?”
  • “Why is password entropy important?”
  • “What is the difference between password complexity and entropy?”
  • “What’s your opinion on NIST’s 2017 password guidelines?”

If they can’t answer these clearly and specifically without use of overly complex technical language, keep looking. (Many parts of this blog are overly technical for many audiences)

Notes

Organizations continue enforcing 90-day password rotations and complexity requirements despite NIST deprecating these practices in 2017. The disconnect exists because security decisions are made by people who don’t understand entropy calculations or real-world attack vectors. If your security consultant recommends SHA256 for password storage or can’t explain why length beats complexity, find someone who can. The gap between correct password practices and actual implementation is where most breaches happen.

[references]
Standards and Guidelines:
  1. [1.1]
    NIST SP 800-63B — Digital Identity Guidelines - Authentication and Lifecycle Management
  2. [1.2]
    OWASP Password Storage Cheat Sheet — Best practices for password hashing and storage
Technical Resources:
  1. [2.1]
    Have I Been Pwned — Check if passwords appear in known breaches
  2. [2.2]
    Argon2 Documentation — Password Hashing Competition winner
[changelog]
2025-01-20:
  • Initial publication

Disclaimer

Use the information provided here at your own risk, but if you find errors or issues in this guide, leave a comment and I’ll try to address them ASAP.

RO

Ronaldo

BSc Mathematics | MSc Information Security

  • Technical problem-solver with unusually broad capabilities & interests
  • I figure things out and get things done

Comments

Table of Contents