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.xlsxor 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.
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.
ZU$Xgb8!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.
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.
Complexity usually refers to the range of characters used in a password.
From our earlier examples:
ZU$Xgb8!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
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 of a password can be calculated using the following formula:
where:
- = length of the password (number of characters)
- = number of possible symbols for each character (the size of the character set)
- = number of bits of entropy per character
The result 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.
Now let’s go back to my previous example of ZU$Xgb8! vs fishwithahat and calculate their entropy.
Password Entropy Calculation
Where:
- = length of the password
- = number of possible symbols for each character
1. ZU$Xgb8! (Complex Password)
Analysis:
- Length : 8 characters
- Character sets used:
- Uppercase letters: 26
- Lowercase letters: 26
- Digits: 10
- Special characters: 32 (common symbols)
- Total symbols = 94
Calculation:
2. fishwithahat (Simple Password)
Analysis:
- Length : 12 characters
- Character sets used:
- Lowercase letters only: 26
- Total symbols = 26
Calculation:
Key Insight: Despite appearing “weaker,” the longer password fishwithahat has ~8% more entropy (56.4 vs 52.4 bits) than the “complex” shorter password.
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)
| Aspect | Example A: Forced Complexity (8 chars) | Example B: Simple but Long (12 chars) |
|---|---|---|
| Typical Result | Password1! | correcthorsebattery |
| Policy Assumption | quadrillion combinations (all ASCII printable characters) | trillion combinations (lowercase letters only) |
| User Behavior Reality | Pattern: [Capital] [lowercase word] [number] [symbol] | Pattern: Multiple dictionary words |
| Actual Components | • common words • possible numbers • common end symbols !,@,#,$,% | • common words per position • word combinations • No forced patterns |
| Real Search Space | 100 thousand combinations | 8 billion combinations |
| Security Reality | Weaker - Entropy gap: bits - Highly predictable pattern | Stronger - Entropy gap: bits - Less predictable |
| Crack Time Estimate | Minutes with modern GPU | Days to weeks with modern GPU |
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)
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.
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.
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.
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.
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.
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 limitingPassword complexityPassword 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.
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.
If you’re starting from scratch:
- Fix the technical implementation first (proper hashing, salting, secure storage)
- Document your processes (policies, procedures, incident response)
- Implement monitoring and logging (audit trails, breach detection)
- 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
Where:
- = cryptographic hash function
- = set of all finite binary strings (domain)
- = set of all binary strings of length (codomain)
- = output length in bits (digest size)
Computational Properties
1. Deterministic:
2. Efficient Computation:
3. Uniform Distribution:
Security Properties
4. Preimage Resistance (One-wayness):
5. Second Preimage Resistance:
6. Collision Resistance:
Avalanche Effect
For single-bit input changes, approximately half the output bits should flip:
Where:
- is the Hamming distance
- denotes expected value
Complexity Analysis
Lower Bounds:
- Collision attacks: Birthday bound gives complexity
- Preimage attacks: Information-theoretic bound gives complexity
- Generic security: These bounds are optimal for ideal hash functions
Practical Instantiations: SHA-256 (), SHA-3 ()
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 is essentially mapping any sized data to fixed sized outputs while retaining the security and computational properties mentioned earlier.
-
Same input always gives same output
-
Tiny changes create completely different outputs - Change one letter in
fishwithahatand roughly half the hash bits flip, making it look totally unrelated. -
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
-
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
Where:
- = salted hash function
- = message/password to be hashed
- = random salt value of length bits
- = concatenation operator
- = underlying cryptographic hash function
Security Enhancement
Rainbow Table Resistance:
Unique Hash Property:
Dictionary Attack Mitigation:
Where:
- = size of password dictionary
- = salt length in bits
Salt Requirements
Uniqueness: Each password should use a cryptographically random salt Length: Minimum 128 bits () 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_id | salt | password_hash |
|---|---|---|
| 1 | x7K9mP2q | a1b2c3d4e5f6… |
| 2 | n4R8sL1w | 9z8y7x6w5v4u… |
| 3 | m8T5pL9r | 7m4k8n2p3q1s… |
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
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
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:
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.
| Algorithm | Speed | Salt Support | Cost Parameter | Recommended Setting | Recommended |
|---|---|---|---|---|---|
| MD5 | Very Fast | Manual | None | N/A | NO |
| SHA1 | Fast | Manual | None | N/A | NO |
| SHA256 | Fast | Manual | None | N/A | For non-passwords |
| bcrypt | Slow | Built-in | Rounds (4-15) | 12-14 (250ms target) | YES |
| scrypt | Slow | Built-in | N, r, p params | N=32768, r=8, p=1 | YES |
| Argon2 | Slow | Built-in | t, m, p params | t=3, m=64MB, p=1 | YES (newest) |
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.
You cannot use MD5 or SHA1 for passwords.
MD5can be computed at over 50 billion hashes per second on a modern GPU.SHA1isn’t much better at around 20 billion per second.
These were designed for file integrity, not password security.
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 , 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: delay = barely noticeable
- Attacker cost: attempts = hours for 1 million guesses
- Compare to MD5: Same 1 million guesses = 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:
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):
| Algorithm | Hashes/Second | Time for 1B guesses |
|---|---|---|
| MD5 | 50 billion | 0.02 seconds |
| SHA256 | 20 billion | 0.05 seconds |
| bcrypt (cost 12) | 1,000 | 31.7 years |
| Argon2 | 500-2000 | 15-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.
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 Category | Template | Example 1 | Example 2 | Example 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# |
When your security policy forces users into these patterns, you’re not getting possible passwords. You’re getting maybe 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 Type | Components | Search Space Calculation | Actual Combinations | vs Theoretical 8-char |
|---|---|---|---|---|
[Word][Year][Symbol] | 2000 words × 10 years × 5 symbols | |||
[Month][Year][Symbol] | 12 months × 10 years × 5 symbols | |||
[Company][Year][Symbol] | 500 companies × 10 years × 5 symbols | |||
[Name][BirthYear][Symbol] | 1000 names × 80 years × 5 symbols | |||
[L33t][Numbers][Symbol] | 1000 l33t words × 1000 nums × 5 symbols | |||
[Sequential][Numbers][Symbol] | 20 sequences × 1000 nums × 5 symbols | |||
| Average Pattern | Combined realistic patterns | |||
| Theoretical 8-char | All printable ASCII |
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.
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.
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.
- • 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
- • 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.
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.
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] ▸
-
[1.1]
NIST SP 800-63-4 — Digital Identity Guidelines - Authentication and Lifecycle Management (2025)
-
[1.2]
OWASP Password Storage Cheat Sheet — Best practices for password hashing and storage
-
[2.1]
Have I Been Pwned — Check if passwords appear in known breaches
-
[2.2]
Argon2 Documentation — Password Hashing Competition winner
-
[3.1]
Cryptography Fundamentals — Complete guide to symmetric and asymmetric cryptography
-
[3.2]
Passkeys Explained — How FIDO2 replaces passwords with public-key cryptography
-
[3.3]
Password Managers — Why you need a password manager and which one to use
[changelog] ▸
- → 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.
Comments