There is an uncomfortable truth that almost no service tells you: the server where you register should not be able to know your password. If a website can show it to you under “forgot password”, that is a warning sign, not a courtesy. Behind that claim lies an entire branch of applied cryptography worth understanding: hashing, the one-way process that turns a password into a string that cannot be reversed.
Why is encrypting it not enough?
Intuition says: store the password encrypted and done. The problem is that encryption is reversible by design. Encryption (with symmetric algorithms such as AES) exists so that whoever holds the correct key can recover the original data. If an attacker steals the database and also the encryption key (which usually lives on the same server or in memory), they have your plaintext passwords. Hashing attacks the problem from another angle: it uses a function that only works one way.
What a hash function is and why it is one-way
A hash function (SHA-256, for example) takes an input of any length and produces a fixed-length output, the digest. Three properties make it useful here. First, it is deterministic: the same input always yields the same digest, allowing verification without storing the original. Second, it is irreversible: mathematically there is no inverse operation that, starting from the digest, recovers the input; you can only try candidates. Third, it has the avalanche effect: changing a single bit of the input produces a completely different digest, so two similar passwords leave no similar traces.
Checking a login then becomes trivial in theory: compute the hash of what the user types and compare it with the stored one. If they match, the password was correct. The plaintext password is never compared with anything.
The big mistake: hashing without a salt
Hashing is not enough. If two users choose “123456”, their digests are identical, which immediately betrays the weakness. Worse, attackers maintain rainbow tables: precomputed lists of hashes of millions of common passwords. Against SHA-256, computing billions of hashes per second is trivial on a GPU, so any weak password falls within minutes via a simple dictionary attack.
The defense is the salt: a random string, unique per user, concatenated to the password before hashing. Because each salt is unique, two users with the same password produce different digests, rainbow tables become useless (you would have to precompute one table per possible salt), and the salt can be stored in plaintext next to the hash without weakening it.
Why “fast” functions are the wrong tool
Here appears the central design paradox: for encryption we want speed, but for storing passwords we want exactly the opposite. If hashing is instantaneous, trying millions of candidates is too. That is why SHA-256 and MD5 are not used, but functions that are deliberately slow and tunable in cost.
- bcrypt: embeds the salt and a cost factor; each round runs the Blowfish algorithm hundreds or thousands of times. Raising the cost factor makes the computation expensive for both the legitimate user and the attacker.
- PBKDF2: applies the HMAC function thousands of times iteratively. It underlies standards such as WPA2, and its cost is tuned with the iteration count.
- Argon2: winner of the Password Hashing Competition (2015) and the current recommendation. It adds two cost dimensions the earlier ones lack: it is memory-hard (it needs a configurable amount of RAM) and parallelism-resistant. This hits exactly where it hurts the attacker, who relied on massively parallel GPUs to try passwords at once. ASICs and GPUs have limited RAM, so raising memory per attempt drastically limits how many candidates can be tried per second.
This is what is called a key derivation function: besides hiding the password, it transforms it in a costly way to frustrate brute force.
Comparing without leaking information
There is one last elegant detail. When verifying a login, the server compares the computed hash with the stored one. If that comparison runs character by character and stops at the first difference, the response time reveals how many characters match: that is the timing attack. The fix is a constant-time comparison that always walks the whole string and never stops early, so the duration does not depend on the content. Languages and libraries expose dedicated functions for this (such as hash_equals in PHP or crypto.timingSafeEqual in Node.js).
What this means for you
Understanding this changes how you choose passwords. Since the server never knows them, an “honest” website can never recover yours: it can only let you create a new one. That is why password managers are the recommended practice: they generate long, unique, random passwords and encrypt them in a local file (the vault) unlocked only with your master password, which in turn is derived with Argon2. It also explains why reusing passwords is lethal: when a poorly secured website is breached and stores passwords in plaintext or with fast hashes, attackers try those credentials on every other service. A leak from an obscure site can compromise your bank.
The next time a service tells you it “cannot recover your password”, do not read it as an annoyance. Read it as proof that someone did their job properly.





