How Passwords Are Protected Hashing Salt Pepper

Posted on July 20, 2026 by Vishesh Namdev
Python C C++ Javascript React JS
How Passwords Are Protected in Real Apps - Hashing, Salt and Pepper Explained

How Passwords Are Protected in Real Apps | Hashing, Salt & Pepper 🔐 Ever wondered what actually happens to your password the moment you hit "Sign Up"? No serious application ever stores your password as plain text. Instead, real-world apps use a combination of password hashing, salting, and sometimes peppering to make sure that even if a database is leaked, attackers can't easily recover the original passwords.

In this tutorial, we will learn:

  • Why storing plain text passwords is dangerous
  • What password hashing is and how it works
  • Why hashing alone isn't enough — the problem of rainbow tables
  • What a salt is and how it protects against precomputed attacks
  • What a pepper is and how it differs from a salt
  • Implementing secure password hashing in Node.js using bcrypt
  • Verifying a login password against its stored hash
  • ---

    Why Not Just Store Passwords as Plain Text?

    If a hacker ever gains access to a database that stores plain text passwords, every single user account is instantly compromised — and since many people reuse passwords across sites, the damage spreads far beyond just your app. This is why storing raw, readable passwords is considered one of the worst security mistakes a developer can make. The solution is to never store the actual password at all — only a scrambled, irreversible version of it.

    ---

    What Is Password Hashing?

    Hashing is a one-way mathematical function that converts a password like MyPassword123 into a fixed-length string of random-looking characters, called a hash. The key property of a good hashing algorithm is that it is irreversible — you can't take the hash and work backward to get the original password. When a user logs in, the app hashes the entered password again and compares the two hashes, instead of comparing raw passwords.

    // A simplified idea of hashing (not real code, just for understanding)
    password = "MyPassword123";
    hash = hashFunction(password);
    // hash => "e3f1c2...9a7b" (stored in database, not the real password)
    ---

    The Problem With Plain Hashing: Rainbow Tables

    Hashing alone has a weakness. Attackers can precompute the hashes of millions of common passwords (like 123456 or password) and store them in giant lookup tables called rainbow tables. If two users pick the same password, their hashes will be identical — so an attacker only needs to crack it once to unlock every matching account. This is exactly the gap that salting is designed to close.

    ---

    What Is a Salt?

    A salt is a random string of characters generated uniquely for every single user, which gets combined with their password before hashing. Because the salt is different for every user, even two identical passwords produce completely different hashes. The salt is stored alongside the hash in the database — it doesn't need to be secret, it just needs to be unique and random.

    // Conceptual example
    salt = generateRandomString();          // unique per user, e.g. "x7Ak2p"
    saltedPassword = password + salt;       // "MyPassword123x7Ak2p"
    hash = hashFunction(saltedPassword);     // stored along with the salt
    ---

    What Is a Pepper — and How Is It Different From a Salt?

    A pepper is also a random value added to the password before hashing, but with one key difference: it is not stored in the database. Instead, it's kept somewhere separate — typically as an environment variable or in a secrets manager. The same pepper is usually shared across all users of the app. This way, even if the entire database is stolen, the attacker still doesn't have the pepper needed to crack the hashes.

    Concept Unique Per User? Stored In Purpose
    Hash N/A Database One-way scrambled version of the password
    Salt Yes Database (next to the hash) Prevents identical passwords from producing identical hashes
    Pepper No (shared across app) Environment variable / secrets manager Adds a secret layer that isn't exposed even if the database leaks
    ---

    Step 1: Install bcrypt in Your Node.js Project

    In real applications, we don't write our own hashing logic — we use a well-tested library like bcrypt, which automatically handles salting for us internally.

    npm install bcrypt
    ---

    Step 2: Hashing a Password on Signup

    When a user signs up, we hash their password (combined with our secret pepper) before saving it to the database. bcrypt automatically generates and stores a unique salt as part of the resulting hash string.

    const bcrypt = require("bcrypt");
     
    const PEPPER = process.env.PASSWORD_PEPPER; // stored securely, not in the database
    const SALT_ROUNDS = 10;
     
    async function registerUser(email, plainPassword) {
      const pepperedPassword = plainPassword + PEPPER;
     
      const hashedPassword = await bcrypt.hash(pepperedPassword, SALT_ROUNDS);
     
      // Save `email` and `hashedPassword` to the database
      return { email, hashedPassword };
    }
    ---

    Step 3: Verifying a Password on Login

    When the user logs in, we re-apply the same pepper to the entered password and let bcrypt.compare handle extracting the salt and checking it against the stored hash.

    async function loginUser(enteredPassword, storedHash) {
      const pepperedInput = enteredPassword + PEPPER;
     
      const isMatch = await bcrypt.compare(pepperedInput, storedHash);
     
      if (isMatch) {
        console.log("Login successful ✅");
      } else {
        console.log("Invalid credentials ❌");
      }
     
      return isMatch;
    }
    ---

    Why bcrypt Instead of a Simple Hash Like MD5 or SHA-256?

    General-purpose hash functions like MD5 or SHA-256 are built for speed, which is exactly the wrong property for password storage — a fast hash means an attacker can try billions of guesses per second on stolen hashes. bcrypt (along with alternatives like scrypt and Argon2) is intentionally slow and configurable via a cost factor (SALT_ROUNDS), making brute-force attacks far more expensive and impractical, even with modern hardware.

    ---

    Features and Learnings:-

  • Understood why plain text password storage is a critical security risk.
  • Learned what hashing is and why it's a one-way, irreversible process.
  • Understood the rainbow table attack and why plain hashing isn't enough on its own.
  • Learned what a salt is and how it makes every user's hash unique.
  • Learned what a pepper is and how it differs from a salt by staying outside the database.
  • Installed and used bcrypt to hash passwords securely in Node.js.
  • Implemented a real signup flow that hashes passwords with a salt and pepper.
  • Implemented a real login flow that verifies passwords using bcrypt.compare.
  • Understood why slow, purpose-built algorithms like bcrypt are safer than fast hashes like MD5 or SHA-256.
  • 📢 Important Note 📢

    How did you feel about this post?

    😍 🙂 😐 😕 😡

    Was this helpful?

    👍 👎