We practice what we preach

Cookieless analytics, fully managed by ByteRon. No personal data collected, no invasive tracking. Ever.

Privacy Policy →

Everything about passwords

Published: at 10:00

Everything About Passwords

TL;DR

Most password policies are backwards and make things less secure. I’ll show you the math behind fishwithahat vs ZU$Xgb8! and which one is mathematically more secure, explain how passwords actually get hacked, and give you practical policies that don’t suck.

Prerequisites:

  • None - suitable for all technical levels
  • Some parts require Mathematical literacy
  • Basic understanding of authentication concepts helpful but not required
  • Optional: Cryptography Fundamentals for deeper understanding of hashing

What you’ll learn:

  • Password entropy calculations and why length beats complexity
  • How passwords actually get compromised
  • 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 ever worked with a computer for any company in the last 20 years, 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 in your personal or professional life?

  • 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

Or even worse, have you encountered some of these?

  • Password sharing for shared SaaS accounts
  • Password reuse across multiple accounts (everyone does this)
  • Sending passwords through Slack, Teams, email, or sticky notes
  • Storing passwords in .txt .xlsx or anything like that

Here’s the thing: there is a massive disconnect between “experts” offering advice on secure authentication systems and the reality of user behaviour. You might even think that these policies are created by people who 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 behaviour.

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 TL;DR

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 $, 8 and capitalized letters.

At the same time fishwithahat is less complex since it uses only lowercase 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 LL or the size of the character set NN increases the entropy and thus the password strength.

Now let’s go back to my previous example of ZU$Xgb8! vs fishwithahat and calculate their entropy.

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: 8 characters
  • Character sets used:
    • Uppercase letters: 26
    • Lowercase letters: 26
    • Digits: 10
    • Special characters: 32 (common symbols)
  • Total symbols NN = 94

Calculation:

H=8×log2948×6.554652.44 bitsH = 8 \times \log_2 94 \approx 8 \times 6.5546 \approx \mathbf{52.44 \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 ~8% more entropy (56.4 vs 52.4 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: Simple but long (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 - 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 a 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.

Online vs Offline: The Critical Distinction

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.

In practice, 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 technique to prevent automated attacks 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 completely 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. You would never use SHA-256 for password hashing, the only reason I use it for this example is because it’s the most widely known hashing algorithm.

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)"]

    class F warning
    class I amber

We can more generically visualise this process as inputting 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 e.g 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 the 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..."]

    class C primary

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)"]

    class B primary
    class B2 teal
    class E warning
    class E2 amber
⚠️ 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 at least 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} \| \text{salt} \| \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]

    class C primary
    class E purple
    class I warning
    class K info
    class L,M,N,O process
    class S success
    class T danger

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 logs in 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 databases 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.

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 use cases

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

⚠️ 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 attacker’s life more difficult.

Real-world cracking speeds (RTX 4090 GPU):

AlgorithmHashes/SecondTime for 1B guesses
MD550 billion0.02 seconds
SHA25620 billion0.05 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.

None of this is new. NIST SP 800-63B addressed all of these points back in 2017 - deprecating forced rotation, dropping composition rules, and recommending length over complexity.

2017 recommendations vs 2026 reality

NIST published these guidelines in 2017, yet we are in 2026 and most organisations still haven’t adopted them. Forced 90-day rotations, 8-character minimums, and complexity rules remain the norm.

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

Password Policies That Actually Work

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, no maximum
  • • All characters allowed (including spaces)
  • • No composition rules
  • • Cannot appear in breached password databases
  • • Change only when compromise suspected
  • • MFA required for all accounts
  • • Progressive lockout (exponential backoff)
  • • Password manager supported

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

Note that I am not mentioning anything about their storage, salting or hashing. That’s because they do not affect the end user experience, however the importance of storage, salting and hashing cannot be understated.

My focus for this policy is both improving the security for the business while also improving the end user experience.

💡 Password managers

I also recommend using a password manager. A good password policy combined with a password manager is the best approach in 2026.

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

ℹ️ The Future: Passkeys

Passwords are fundamentally flawed - they’re a shared secret that can be phished, stolen from breached databases, and stuffed across sites. Passkeys (FIDO2/WebAuthn) replace this with public-key cryptography where the private key never leaves your device. There’s nothing to phish, nothing to crack from a breach.

NIST SP 800-63-4 now requires phishing-resistant authentication options, and passkeys qualify. Everything in this article remains essential for the transition period and fallback authentication, but the future is passwordless.

I cover passkeys in depth in my passkeys blog.

[references]
Standards and Guidelines:
  1. [1.1]
    NIST SP 800-63-4 — Digital Identity Guidelines - Authentication and Lifecycle Management (2025)
  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
Related Articles:
  1. [3.1]
    Cryptography Fundamentals — Complete guide to symmetric and asymmetric cryptography
  2. [3.2]
    Passkeys Explained — How FIDO2 replaces passwords with public-key cryptography
  3. [3.3]
    Password Managers — Why you need a password manager and which one to use
[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