Hashing and Encryption
Hashing and encryption both transform data, but they solve fundamentally different problems. Encryption is two-way: you lock data with a key and unlock it with a key. Hashing is one-way: you put data in and get a fixed-length fingerprint out, with no way to reverse it. Mixing them up — encrypting when you should hash, or hashing when you need the data back — is one of the most common security mistakes in application development.
Encryption vs Hashing
Encryption (Reversible)
Plaintext
"secret message"
Encrypt + Key
AES-256 / RSA
Ciphertext
a4f8b2...c9e1
Decrypt + Key
same key (symmetric)
Plaintext Recovered
"secret message"
Round-trip complete
Hashing (One-Way)
Plaintext
"secret message"
Hash Function
SHA-256 / bcrypt
Fixed-Length Digest
e3b0c4...98cd
No Reverse Possible
one-way function
Cannot recover plaintext
Avalanche Effect — One character changes everything
"hello"
2cf24d...71e31d
"hellp"
f3a9c1...8b47e2
Completely different digests — no pattern leakage
Cracking Speed — Why bcrypt exists
bcrypt is intentionally slow — that is the feature
Encryption protects data in transit — hashing protects stored passwords
Hashing — One-Way, Deterministic, Fixed-Length
A hash function takes input of any size and produces a fixed-length output. SHA-256 always produces 256 bits, whether you hash a single character or an entire database dump. The same input always produces the same output (deterministic), and even a tiny change in the input produces a completely different hash (the avalanche effect).
SHA-256("hello") → 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA-256("hello.") → 3338c47c3ea72ee82b41e782e5e0a83538dda04a59fda5f0b0a51ad70ebf30e4
One period added to the input, and the output is unrecognizably different. This property makes hashes useful for integrity verification — if you hash a file before and after transfer and the hashes match, the file wasn't corrupted or tampered with.
The critical property: you cannot recover the original input from the hash. There's no key, no secret, no reverse function. This is what makes hashing appropriate for passwords.
Why You Hash Passwords (Never Encrypt Them)
If you encrypt passwords, anyone with the encryption key can decrypt them all. That key becomes a single point of catastrophic failure — one breach, one leaked key, and every password in your database is exposed in plaintext.
When you hash passwords, even a complete database breach doesn't directly reveal them. An attacker gets hashes, not passwords. To find the original password, they'd have to hash guesses until one matches — which brings us to the problem with fast hashes.
Fast Hashes Are Wrong for Passwords
SHA-256 is a great hash function. It's also a terrible choice for passwords. The problem is speed: a modern GPU can compute billions of SHA-256 hashes per second. An attacker with a few GPUs can try every possible 8-character password in hours.
This is why password hashing uses deliberately slow algorithms: bcrypt, scrypt, and Argon2. These functions include a tunable work factor that controls how much CPU (and in scrypt/Argon2's case, memory) each hash computation requires.
# bcrypt with work factor 12 — takes ~250ms per hash
$2b$12$LJ3m4ys3Lg2VE5DeCPsmq.MkCMRq9APMJJkiQ5YJNFU5y1h.3/bW
# Increasing the work factor by 1 doubles the computation time
# Work factor 13 → ~500ms, 14 → ~1s, etc.
At 250ms per hash, an attacker can try 4 passwords per second instead of billions. That's the entire point — making brute force economically infeasible.
Salt and Pepper
Salt is a random value generated per user and stored alongside the hash. Without salt, two users with the same password produce the same hash, and an attacker can use precomputed lookup tables (rainbow tables) to crack them all at once.
hash("password123" + "random-salt-user-A") → unique hash A
hash("password123" + "random-salt-user-B") → unique hash B
Same password, different salts, different hashes. Each password must be attacked individually. The salt isn't secret — it's stored right next to the hash. Its job is to make precomputation useless, not to be hidden.
Pepper is an application-wide secret that's mixed into the hash but stored separately (in a config file, environment variable, or secret manager — not in the database). If an attacker steals only the database, they're missing the pepper and can't even begin cracking. It's a defense-in-depth measure, not a replacement for salting.
Encryption — Two-Way, Key-Dependent
Encryption transforms plaintext into ciphertext using a key. With the correct key, you can reverse the process and recover the original data. You encrypt data you need back: messages, files, database fields containing PII.
Symmetric encryption (AES) uses the same key for encryption and decryption. It's fast and efficient. The challenge is key distribution — how do you get the key to the other party securely?
Asymmetric encryption (RSA, ECC) uses a key pair: a public key to encrypt and a private key to decrypt. Anyone can encrypt a message with your public key, but only you can decrypt it with your private key. It's slower than symmetric encryption but solves the key distribution problem.
TLS uses both. When your browser connects to a server, they use asymmetric encryption to securely exchange a symmetric session key, then switch to symmetric encryption for the actual data transfer. Asymmetric for the handshake (solves key distribution), symmetric for the data (solves performance).
HMAC — Hashing with a Key
HMAC (Hash-based Message Authentication Code) combines a hash function with a secret key to produce an authentication code. It proves both integrity (the message hasn't been altered) and authenticity (it came from someone who knows the key). Webhook signatures typically use HMAC — the sender computes an HMAC of the payload with a shared secret, and the receiver recomputes it to verify.
Digital Signatures
Digital signatures combine hashing and asymmetric encryption. You hash the message, then encrypt the hash with your private key. The recipient decrypts the hash with your public key and compares it to their own hash of the message. If they match, the message is intact and came from you. This provides integrity, authenticity, and non-repudiation.
Encoding Is Not Encryption
Base64 is not encryption. It's encoding — a way to represent binary data as ASCII text. There's no key, no security, no secret. Anyone can decode it instantly. If you see a "token" that starts with eyJ, that's a base64-encoded JSON object (probably a JWT header), and its contents are completely readable by anyone.
echo "eyJhbGciOiJIUzI1NiJ9" | base64 -d
# Output: {"alg":"HS256"}
Treating encoding as encryption is a real vulnerability that shows up in production systems more often than you'd hope.
Real World: LinkedIn 2012
In 2012, LinkedIn's password database was breached — 6.5 million password hashes leaked. They were using SHA-1 with no salt. Within days, security researchers had cracked the vast majority of them using rainbow tables and brute force.
The failures were compounding: SHA-1 is fast (bad for passwords), there was no salt (rainbow tables work), and the work factor was effectively zero. Had LinkedIn used bcrypt with per-user salts, the same breach would have yielded hashes that were practically useless to the attackers. The data would still have been stolen, but the passwords would have remained protected.
Key Takeaways
- Encryption is reversible (two-way with a key); hashing is irreversible (one-way). Use encryption for data you need back, hashing for data you only need to verify.
- Never encrypt passwords — hash them with bcrypt, scrypt, or Argon2. Fast hashes like SHA-256 are wrong for passwords because GPUs can brute-force billions per second.
- Salt is per-user and defeats rainbow tables; pepper is app-wide and adds defense-in-depth.
- TLS uses asymmetric encryption for key exchange and symmetric encryption for data — combining the strengths of both.
- Base64 encoding is not encryption. If your security depends on someone not knowing how to decode base64, you have no security.
- The LinkedIn breach demonstrated exactly what happens when you use fast, unsalted hashes for passwords: millions cracked in days.
Next up: What is TLS — how encryption actually works in transit, from the handshake to the session key.
Enjoyed this breakdown?
Get new lessons in your inbox.