Password Hashes Explained: How Passwords Are Stored Securely
Learn how password hashing works, the difference between hashing and encryption, common algorithms, and best practices for secure password storage.
The Breach That Exposed 6.5 Million Unprotected Passwords
In 2012, LinkedIn suffered a data breach that leaked 6.5 million password hashes. The company had stored them using unsalted SHA-1 — a fast hash with no random salt added. Within days, most passwords were cracked. The same password that protected a LinkedIn account could unlock banking, email, and corporate systems across the internet. This breach became a textbook case for why password hashing done wrong is almost as bad as storing passwords in plaintext.
Password hashing is the process of converting a password into a fixed-length string of characters using a one-way mathematical function. Unlike encryption, hashing is irreversible — you cannot derive the original password from its hash.
Prerequisites
Before studying password hashes, you should understand:
Hashing vs Encryption
Hashing (One-Way)
Password -> Hash Function -> Hash Value
Hash Value -> Hash Function -> Cannot reverse
Encryption (Two-Way)
Password -> Encryption -> Ciphertext
Ciphertext -> Decryption -> Password (with key)
Common Hashing Algorithms
MD5 (Message Digest 5)
# Generate MD5 hash
echo -n "password123" | md5sum
# Result: 482c811da5d5b4bc6d497ffa98491e38
MD5 produces 128-bit (32 character) hashes. It is cryptographically broken and should never be used for passwords.
SHA-1 (Secure Hash Algorithm 1)
# Generate SHA-1 hash
echo -n "password123" | sha1sum
# Result: cbfdac6008f9cab4083784cbd1874f76618d2a97
SHA-1 produces 160-bit (40 character) hashes. It is also broken for security purposes.
SHA-256
# Generate SHA-256 hash
echo -n "password123" | sha256sum
# Result: efccecda192e9846eb1e8349051cf092b24ee3b8dbba83897b31f404e58f5abd
SHA-256 produces 256-bit (64 character) hashes. It is still secure for integrity checking but not suitable alone for passwords.
Why Simple Hashing is Not Enough
Problem 1: Same Password, Same Hash
password123 -> MD5: 482c811da5d5b4bc6d497ffa98491e38
password123 -> MD5: 482c811da5d5b4bc6d497ffa98491e38
If two users have the same password, they have the same hash. Attackers can see this and know those users share a password.
Problem 2: Rainbow Tables
Attackers precompute hashes for common passwords and store them in lookup tables called rainbow tables. A hash can be reversed by looking it up:
| Password | MD5 Hash |
|----------|----------|
| password123 | 482c811da5d5b4bc6d497ffa98491e38 |
| 123456 | e10adc3949ba59abbe56e057f20f883e |
| qwerty | d8578edf8458ce06fbc5bb76a58c5ca4 |
Problem 3: Speed
Modern hashes like MD5 and SHA-256 are designed to be fast. Attackers can calculate billions of hashes per second using GPUs:
# Hashcat benchmark for MD5
hashcat -b --benchmark-all -m 0
# Result: Billions of hashes per second on modern GPUs
Password-Specific Hashing Algorithms
bcrypt
bcrypt is designed specifically for password hashing. It includes a work factor that makes it intentionally slow:
import bcrypt
# Hash a password (work factor 12 = 2^12 rounds)
password = b"secure_password123"
salt = bcrypt.gensalt(rounds=12)
hashed = bcrypt.hashpw(password, salt)
# Result: $2b$12$LJ3m4ys3Lk5Hxq8yQ9x7Qu4tX0c0pQzLk5Hxq8yQ9x7Q
# Verify password
bcrypt.checkpw(password, hashed)
# Returns: True
Argon2
Argon2 is the winner of the Password Hashing Competition and the current gold standard:
from argon2 import PasswordHasher
ph = PasswordHasher(
time_cost=3, # Number of iterations
memory_cost=65536, # Memory usage in KB (64MB)
parallelism=4, # Number of threads
hash_len=32, # Output hash length
salt_len=16 # Random salt length
)
# Hash a password
hashed = ph.hash("secure_password123")
# Result: $argon2id$v=19$m=65536,t=3,p=4$SALT$HASH
# Verify
ph.verify(hashed, "secure_password123")
PBKDF2 (Password-Based Key Derivation Function 2)
import hashlib
import os
# Generate salt
salt = os.urandom(32)
# Hash with PBKDF2
dk = hashlib.pbkdf2_hmac(
'sha256', # Hash algorithm
b'password', # Password
salt, # Salt
600000, # Iterations (high count)
dklen=32 # Output length
)
Understanding Hash Formats
Linux Shadow File Hashes
# /etc/shadow entry format
username:$algorithm$salt$hash:last_change:min:max:warn:inactive:expire
# $1$ = MD5
# $5$ = SHA-256
# $6$ = SHA-512
# $2y$ = bcrypt
# $argon2id$ = Argon2
# Example (SHA-512)
$6$saltvalue$hashvalue...
# Example (bcrypt)
$2y$12$LJ3m4ys3Lk5Hxq8yQ9x7Qu4tX0c0pQzLk5Hxq8yQ9x7Q
Windows NTLM Hashes
# NTLM hash format (hex string)
# LM:NTLM format
# Example:
Administrator:500:NO PASSWORD*********************:NTLM_HASH::::
# Crack NTLM with hashcat
hashcat -m 1000 ntlm_hashes.txt /usr/share/wordlists/rockyou.txt
Identifying Hash Types
# Using hashid
hashid '482c811da5d5b4bc6d497ffa98491e38'
# Result: MD5
hashid '$2y$12$LJ3m4ys3Lk5Hxq8yQ9x7Qu4tX0c0pQzLk5Hxq8yQ9x7Q'
# Result: bcrypt
# Using hash-identifier
hash-identifier 482c811da5d5b4bc6d497ffa98491e38
Real-World Examples
LinkedIn Breach (2012): 6.5 million unsalted SHA-1 hashes were leaked. Because they lacked salts, most were quickly cracked.
Adobe Breach (2013): 130 million unsalted hashes leaked. Adobe used unhashed password hints and DES-based encryption.
Ashley Madison (2015): 36 million accounts leaked. Used bcrypt for some users but not all, demonstrating inconsistent security practices.
Common Mistakes
Using fast hashes for passwords: MD5, SHA-1, SHA-256 are designed for speed, making them terrible for password storage.
No salting: Without salts, identical passwords produce identical hashes, enabling rainbow table attacks.
Reusing salts: Salt reuse defeats the purpose — each password must have a unique salt.
Self-made cryptography: Custom hash functions are almost always insecure.
Best Practices
Related Tools
Related Articles
Summary
Password hashing is a one-way process that converts passwords into fixed-length hash values. Simple hashing with MD5 or SHA is insufficient due to speed and rainbow table attacks. Purpose-built algorithms like bcrypt and Argon2 include salting and configurable work factors to resist both GPU-based and ASIC-based attacks.