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.
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.
ZU$Xgb8fishwithahat
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$Xgb8fishwithahat
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
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.
Password Entropy Calculation
Where:
- = length of the password
- = number of possible symbols for each character
1. ZU$Xgb8 (Complex Password)
Analysis:
- Length : 7 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 23% more entropy (56.4 vs 45.9 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: Forced Length (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 - Highly predictable pattern | Stronger - Less predictable, more entropy |
| 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 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.
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.
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.
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 completly 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
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 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 “fishwithahat” and 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 that 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..."]
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
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”
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]
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.
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.
| 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 practise, hashing is used for more than just storage of passwords. Each unique usecase for hashing require different properties that benefit that usecase.
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 attackers life more difficult.
Real-world cracking speeds (RTX 4090 GPU):
| Algorithm | Hashes/Second | Time for 1B guesses |
|---|---|---|
| MD5 | 50 billion | 20 seconds |
| SHA256 | 20 billion | 50 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.
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.
- 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.
- 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.
- 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.
- 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.
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.
- • 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, 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.
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
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.
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] ▸
- [1.1]NIST SP 800-63B — Digital Identity Guidelines - Authentication and Lifecycle Management
- [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
[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.