AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

Password Hashing: Keeping Passwords Secure

📚 Security⏱️ 20 min read🎓 Grade 9
✍️ AI Computer Institute Editorial Team Updated: August 2026 CBSE-aligned · Peer-reviewed · 20 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

In December 2009, a company called RockYou — which made small games and widgets for social media sites — was broken into by attackers. The attackers walked away with roughly 32 million user passwords. There was no cracking involved, no guessing, no brute force. RockYou had been storing every single password as plain, readable text in its database. The password 123456 was sitting in a table next to the email address that used it, exactly as the user had typed it. Anyone who stole that database owned every account instantly.

This chapter is about the fix computer scientists built for exactly this problem: a way to let a website check whether you typed the correct password, without the website ever needing to store your actual password anywhere. It sounds almost like a contradiction — how do you check something without keeping a copy of it to compare against? The answer is a tool called a hash function, and understanding it properly is one of the most useful pieces of applied mathematics a Grade 9 CS student can learn, because it shows up everywhere from login pages to blockchain to file integrity checks.

A one-way blender, not a lock and key

Start with a mental picture. A lock and key is reversible: you lock a box with a key, and the same key opens it again. That is how encryption works — you scramble data with a key, and anyone holding the matching key can unscramble it back to the original. Encryption is meant to be undone.

A hash function is different. Think of a kitchen blender. You drop a banana, a spoon of sugar, and some milk into it, and out comes a smoothie. Two things are true about that smoothie: first, if you use the exact same banana, same sugar, same milk, and blend for the same time, you get an identical smoothie every time — the process is completely predictable. Second, once the smoothie exists, there is no way to run the blender backwards and recover the whole banana. The information about the original ingredients hasn't vanished exactly, but it has been irreversibly mixed. You cannot "unblend."

A cryptographic hash function does the same thing to data. It takes an input of any length — a single character or an entire novel — and produces an output of a fixed length, called a hash or digest. Feed it the same input twice, you get the same digest twice. But given only the digest, there is no mathematical shortcut to recover the original input. This property is called pre-image resistance, and it is the entire reason hash functions are useful for passwords: a website can store the digest of your password instead of the password itself, and even if that digest leaks, your actual password does not.

Building a toy hash function by hand

Real hash functions like SHA-256 involve bit-shuffling operations that are tedious to trace by hand, so let's build a deliberately simple — and deliberately weak — toy version first, to see the shape of the idea before trusting the real thing.

Here is a toy hash function: take a word, convert every letter to its position value using the standard character code (so 'a' = 97, 'b' = 98, and so on, using ASCII), add them all up, and take the remainder when divided by 100.

def toy_hash(word):
    total = sum(ord(letter) for letter in word)
    return total % 100

Let's trace it on the word "cat" by hand:

  • ord('c') = 99
  • ord('a') = 97
  • ord('t') = 116
  • Sum = 99 + 97 + 116 = 312
  • 312 % 100 = 12

So toy_hash("cat") = 12. Now try "act" — same three letters, different order:

  • 97 + 99 + 116 = 312
  • 312 % 100 = 12

Also 12. Two completely different words produced the identical hash. This is called a collision, and it is exactly the flaw that makes our toy function useless for security: because it only adds letter values, it doesn't care about their order at all, so any anagram collides. A real password-hashing function must make this kind of collision astronomically unlikely, and it must also make sure that changing even one letter scrambles the output completely, not predictably. Our toy function fails that too — try "dog" (100 + 111 + 103 = 314, so hash 14) and you can see the outputs drift by small, guessable amounts as letters change. A real hash needs the opposite: total unpredictability from tiny changes. That property has a name, and real algorithms are specifically engineered to have it.

The avalanche effect: SHA-256 in practice

SHA-256 is a real, widely used cryptographic hash function (part of the SHA-2 family, designed by the US National Security Agency and published in 2001). It always outputs 256 bits, which is written as 64 hexadecimal characters. Here it is used from Python's standard library, run for real to produce the digests below:

import hashlib

print(hashlib.sha256("password".encode()).hexdigest())
print(hashlib.sha256("Password".encode()).hexdigest())
print(hashlib.sha256("password1".encode()).hexdigest())

The actual output of this code is:

5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
e7cf3ef4f17c3999a94f2c6f612e8a888e5b1026878e4e19398b23bd38ec221a
0b14d501a594442a01c6859541bcb3e8164d183d32937b851835442f69d5c94e

Look closely at what changed between the first two lines. "password" and "Password" differ by exactly one bit of information — a single letter's capitalisation. Their digests share no visible pattern at all; they look like two unrelated random strings. The same happens between "password" and "password1", which differ by only one extra character at the end. This is called the avalanche effect: a one-character change to the input should flip roughly half of the output bits, with no visible relationship between similar inputs and their digests. It is precisely what our toy hash function lacked, and it is what stops an attacker from guessing anything about your password by studying its hash.

SHA-256 has 2256 possible output values — that number, written out, is 115,792,089,237,316,195,423,570,985,008,687,907,853,269,984,665,640,564,039,457,584,007,913,129,639,936, a 78-digit number far beyond everyday intuition (for comparison, current scientific estimates put the number of atoms in the observable universe at around 1080, so this is in the same staggering range). Collisions technically exist somewhere in that space, but finding one by chance is not a realistic attack.

Why hashing alone still isn't safe: rainbow tables

Here's the trap. If a hash function is deterministic — same input always gives the same output — then anyone can pre-compute the hash of every password in a dictionary in advance, once, and store the results in a giant lookup table. This is called a rainbow table: millions of common passwords (123456, iloveyou, qwerty, cricket team names, birth years) get hashed ahead of time and matched against a stolen hash list in seconds, because looking a value up in a pre-built table is nearly instant — no guessing required at login time at all.

This is exactly what happened to LinkedIn in 2012. Their password database was stolen, and the passwords inside had been hashed with SHA-1 — but with no further protection. Attackers matched millions of the leaked hashes against precomputed tables and cracked a huge fraction of them within days, because hashing by itself does nothing to stop this kind of table lookup. The lesson: a hash function being mathematically one-way is necessary but not sufficient. Two users who both chose the password cricket123 will get the exact same hash from plain SHA-256, which means cracking one instantly cracks the other, and a rainbow table built once works forever against any database using that same algorithm.

Salting: making every password unique before it's hashed

The fix is a small piece of randomness called a salt. When you create an account, the server generates a long random string — different for every single user — and hashes it together with your password, not the password alone:

import hashlib, os

password = "cricket123"
salt = os.urandom(16).hex()          # a fresh random salt, e.g. "a91f7c..."
combined = salt + password
stored_hash = hashlib.sha256(combined.encode()).hexdigest()

# The database row stores BOTH the salt and the hash:
# salt      = a91f7c3e9b2d4a1f8e6c0d3b5a7f9e2c
# hash      = (64-character digest of salt+password)

Now, two users who both chose cricket123 get completely different stored hashes, because their salts differ — the avalanche effect guarantees it. A pre-built rainbow table is useless here, because it was computed for plain passwords, not for "this specific random salt plus this password," and attackers would need a fresh table for every single salt, which defeats the entire point of pre-computing anything. The salt itself is not secret — it is stored right next to the hash in the database, in plain view — because its job isn't to hide anything, it's to make every hash unique, forcing an attacker to attack accounts one at a time instead of all at once.

Why hashing must also be slow: key stretching

Salting stops pre-computed tables, but it doesn't stop an attacker who steals your database from guessing passwords one at a time against a specific stolen hash — trying password1, then password2, then every word in a dictionary, hashing each guess and comparing. SHA-256 is deliberately built for speed, because it's also used for things like verifying downloaded files or securing bank transactions, where speed matters. That's a problem for passwords specifically: modern graphics cards can compute billions of SHA-256 hashes per second, which means a six-character password can be brute-forced in a very short time even with proper salting.

The fix is to use a hash designed to be deliberately, tunably slow — algorithms built for this exact job, like bcrypt, PBKDF2, and Argon2. The core idea behind all three is called key stretching: instead of hashing the password once, you hash the result again, and again, thousands or hundreds of thousands of times, so that computing one guess takes a deliberately noticeable fraction of a second instead of a few nanoseconds. Here's a simplified version of the idea, built out of ordinary SHA-256 calls, to show the mechanism (real libraries like bcrypt use more carefully engineered internals, but the looping principle is the same):

import hashlib

def stretched_hash(password, salt, rounds=100000):
    value = salt + password
    digest = value.encode()
    for _ in range(rounds):
        digest = hashlib.sha256(digest).digest()
    return digest.hex()

If checking one password guess takes even 5 milliseconds instead of 5 nanoseconds, that is a million-times slowdown for the attacker trying every possibility, while being completely unnoticeable to a real user logging in once. This "work factor" can be tuned upward over the years as computers get faster — which is exactly why modern password systems don't just use plain SHA-256 at all, however strong SHA-256 is as a general-purpose hash function.

Correcting a common misconception

Students very often say "the website encrypts my password" — and this mixes up two genuinely different tools. Encryption is reversible given the right key: banks encrypt your transaction data so their servers can decrypt and read it later. Hashing is one-way by design: there is no key that turns a password hash back into the password, not even for the website that created it. This is exactly why, when you click "Forgotten password" on a well-built site, it never emails you your old password back — it can't, because it never had it in a recoverable form. It only ever had a hash. A site that can email you your existing plaintext password is quietly telling you it is doing something insecure behind the scenes.

A second, subtler misconception: "SHA-256 is a strong, secure algorithm, so it must be great for passwords." SHA-256 genuinely is cryptographically strong — pre-image resistant, collision resistant, with the avalanche effect verified above. But "strong" and "suitable for password storage" are different questions. SHA-256's speed, which is a strength for verifying file downloads, is a weakness for password storage, because speed is exactly what lets an attacker brute-force stolen hashes quickly. This is why security engineers reach for deliberately slow, tunable algorithms like bcrypt for passwords specifically, while still using fast hashes like SHA-256 for other jobs.

The complete picture: registration and login

Putting salting and slow hashing together, here is the full lifecycle of a password on a properly built system — the same shape used by any serious login system, whether it's an email provider, a college portal, or an app you use daily in India for banking, results, or attendance:

How a Server Stores and Checks a Password REGISTRATION (once) LOGIN (every time) You type: "cricket123" Server generates a fresh random salt: a91f7c3e... Slow hash(salt + password) e.g. bcrypt, 100,000+ rounds Database stores: salt + hash (the plain password is never saved) You type: "cricket123" again Server looks up YOUR row and re-uses the SAME stored salt Slow hash(salt + new attempt) using the identical algorithm New hash == stored hash? YES: login allowed NO: access denied Notice: the server never decrypts anything and never recovers your password. It only ever compares two hashes.

Trace the logic carefully, because the whole chapter is really contained in this one diagram. At registration, your password only ever exists in readable form for a fraction of a second, inside the server's memory, while it gets combined with a random salt and pushed through a slow hash function; only the salt and the resulting digest are written to disk. At login, the server does not "look up your password and compare strings" — it repeats the exact same salt-and-hash process on your new attempt and compares the two digests. If a hacker steals the entire database, they get salts and hashes, never passwords, and cracking even one account requires running the slow hash function repeatedly against that one specific salt — no shortcuts, no shared rainbow table, no instant win.

What this means for the password you choose

Good hashing protects the server side of the system, but it cannot rescue a genuinely weak or reused password from a targeted guessing attack — key stretching only multiplies the attacker's cost, it doesn't make guessing impossible. A password like cricket123 is still one of the first thousand guesses any attacker tries, salted and slow-hashed or not, because dictionary attacks target the few million most common human choices, not the entire keyspace. This is precisely why CBSE cyber-safety guidance and real security practice both emphasise long, unpredictable passwords (or better, passphrases) combined with two-factor authentication: hashing defends the database, but choosing an uncommon password defends the guess.

Check your understanding

  1. Using the toy hash function total = sum(ord(letter) for letter in word); return total % 100, compute the hash of the word "bad" by hand, showing each ASCII value. Then find a different three-letter word that collides with it, and explain in one sentence why your toy hash function makes such collisions so easy to find.
  2. Explain, in your own words, why a website that emails you your original password when you click "Forgotten Password" is a warning sign about how it stores passwords.
  3. Two users on the same website both pick the password iloveindia. If the site salts and hashes passwords correctly, will their two stored hash values be the same or different, and why? What would your answer be if the site used plain, unsalted SHA-256 instead?
  4. A site switches from checking passwords with plain SHA-256 to checking them with bcrypt at 100,000 rounds. A single login attempt now takes roughly 50 milliseconds longer for a genuine user than it used to. Explain why this tiny, unnoticeable delay for one honest login is actually the entire point of the change, from an attacker's perspective trying millions of guesses.
  5. A classmate claims: "Hashing and encryption are basically the same thing — they both scramble your data." Identify exactly what is wrong with this statement and correct it in two or three sentences, using the key difference discussed in this chapter.

Summary

  • A hash function takes input of any length and produces a fixed-length digest; it is deterministic (same input, same output) but practically impossible to reverse — unlike encryption, which is designed to be reversed with a key.
  • Good hash functions show the avalanche effect: a one-character change in the input produces a completely different, unpredictable digest, as demonstrated with real SHA-256 outputs for "password", "Password", and "password1".
  • A weak, hand-built hash (like adding up letter values) can suffer collisions — different inputs producing the same output — which is exactly what real cryptographic hashes like SHA-256 are engineered hard against.
  • Hashing alone is not enough: identical passwords produce identical hashes, which attackers exploit with pre-computed rainbow tables — this is what happened in the 2012 LinkedIn breach.
  • Salting adds a unique random value per user before hashing, so identical passwords produce different stored hashes and rainbow tables stop working.
  • Key stretching (used by bcrypt, PBKDF2, Argon2) deliberately runs the hash thousands of times to slow down brute-force guessing, trading an unnoticeable delay for a real user against a massive slowdown for an attacker.
  • A server never "checks your password" by decrypting anything — it hashes your login attempt with your stored salt and compares two digests. The plaintext password should never be recoverable from what the server stores, even by the server's own operators.

Think About It

Think about this: How would you explain password hashing: keeping passwords secure to a friend who has never seen a computer? What real-world analogy would you use? Imagine you had to build a system using these concepts — what would be your first step? Try this: before moving on, write down three things you learned and one question you still have.

← JWT Authentication: Secure Login SystemsCORS: Enabling Cross-Origin Requests Safely →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn