Password Hashing Using Salt and bcrypt: A Developer's Guide to Secure Authentication
bycrypthashingpasswordcodepassword-security5 min read

Password Hashing Using Salt and bcrypt: A Developer's Guide to Secure Authentication

Archit Jain

Archit Jain

Full Stack Developer & AI Enthusiast

Table of Contents


Introduction

Every developer eventually faces the question: how do I store user passwords securely? The stakes are high. A breach exposing unhashed or weakly stored passwords can damage users, businesses, and reputations. In this guide, we’ll unpack the ins and outs of password hashing using salt and bcrypt, giving you the tools you need to protect your users and sleep a little better at night.

We'll break down the theory, sprinkle in real-world code from platforms like Node.js, Python, and Java, and walk through the trade-offs of some popular algorithms. Whether you're just beginning or refining a production login system, this one's for you.


Why Password Hashing Matters

Let’s start with a blunt truth: storing user passwords in plain text is a critical security failure.

If a hacker gains access to your user database and sees something like:

username: john_doe
password: 123456

You're in big trouble.

Even if passwords are slightly masked or obscured, they’re still vulnerable unless hashed and salted properly. To understand the solution, you need to first understand the threat.

Common Types of Attacks on Passwords:

Attack Type Description
Brute-force Tries every possible password combination. Time-consuming but effective.
Dictionary Uses a list of common passwords to guess matches.
Rainbow Tables Precomputed hash tables to reverse known hashes.
Credential Stuffing Uses leaked credentials from one site to try on other platforms.

What all of these attacks exploit is weak, unsalted, or improperly hashed passwords. So let’s build a defense.


Hashing 101: What It Is and Isn’t

What Hashing Does:

  • Takes an input string (like a password)
  • Passes it through a hash function
  • Produces a fixed-length, irreversible string known as a digest
Input: mySecret123
Hashed: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3

What it DOESN’T Do:

  • Hashing is one-way: you can’t "decrypt" a hash
  • If done incorrectly (like without salt), multiple users with the same password have identical hashes

Key Hash Function Characteristics:

Feature Why It Matters
Deterministic Same input will always produce the same output
Collision-resistant No two inputs produce the same hash
Fast for valid use Shouldn't slow down legitimate users
Slow for attackers Should resist brute-force or GPU-based attacks

That last one brings us to the real MVP of this story…


Introducing ‘Salt’ into the Hash

What if two users choose the same password? That’s where salt saves the day.

What is a Salt?

A salt is a random string appended or prepended to the password before hashing. It ensures that even if two users have the same password, their hashes will be completely different.

Password: "swordfish"
Salt: "xu3b@1"
Salted Input: "swordfishxu3b@1"
Hashed: (resulting unique hash)

Why Salting is Critical:

Benefit Impact
Prevents rainbow table attacks Hashes are unique per password per user
Unique hash per entry Even same passwords won’t look the same in DB
Slows down bulk attacks Attackers must compute hashes from scratch each time

Still with me? Good. Because hashing + salting leads us to...


bcrypt: A Hashing Powerhouse

bcrypt illustration

Bcrypt is a specialized password hashing algorithm designed just for this purpose. Invented in 1999 by Niels Provos and David Mazières, bcrypt is slow-on-purpose and includes automatic salting.

bcrypt Features Rundown:

Feature Description
Built-in Salting No need to generate your own salt - bcrypt handles it for you
Adjustable Cost You can tweak 'work factor' to increase hashing difficulty
Slowness-by-design Designed to thwart brute-force attacks on modern CPUs/GPUs
Automated Handling Encodes the salt, cost, and hash in a single string

Format of a bcrypt Hash:

A typical bcrypt hash string looks like:

$2b$12$D4G5f18o7aMMfwasBL7Gpuq97Y3c2Rr5tyjZjkkqz5PU6nluKwNqG

Breaking this down:

  • $2b$ - bcrypt version
  • 12 - cost factor (the power of 2 number of rounds)
  • D4G5f18o7aMMfwasBL7Gpu - base64-encoded salt (22 characters)
  • Rest - actual hashed value

Let’s jump into the practical side of things.


Implementing Bcrypt: Real Code in Real Languages

We’re rolling up our sleeves now to demonstrate how to use bcrypt in Node.js, Python, and Java.


1. Node.js Implementation

Install bcrypt:

npm install bcrypt

Hash a password

const bcrypt = require('bcrypt');
const saltRounds = 12;
const password = "mySecretP@ss";

bcrypt.hash(password, saltRounds, function(err, hash) {
  if (err) throw err;
  console.log("Hash:", hash);
});

Verify a password

const storedHash = "$2b$12$..."; // hash from your DB
const inputPassword = "mySecretP@ss";

bcrypt.compare(inputPassword, storedHash, function(err, isValid) {
  console.log("Does it match?", isValid);
});

Node.js also supports async/await:

const hash = await bcrypt.hash(password, saltRounds);
const match = await bcrypt.compare('guess', hash);

2. Python Implementation

Install the bcrypt library:

pip install bcrypt

Hash a password

import bcrypt

password = b"supersecret123"
salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(password, salt)

print("Hash:", hashed.decode())

Verify a password

if bcrypt.checkpw(b"supersecret123", hashed):
    print("Match!")
else:
    print("No match!")

No need to store the salt separately - bcrypt.hashpw encodes it into the hash.


3. Java Implementation (Spring-based)

Add this Maven dependency:

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-core</artifactId>
  <version>5.8.0</version>
</dependency>

Hash and validate

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); // Default strength 10

String rawPassword = "swordfish";
String hashedPassword = encoder.encode(rawPassword);

boolean matches = encoder.matches("swordfish", hashedPassword);
System.out.println("Do they match? " + matches);

Storing Hashed Passwords in Your Database

When storing a hash:

  • Save the encoded bcrypt string directly
  • No need to keep the salt separately (bcrypt embeds it)
  • Use a VARCHAR(60) column in SQL databases (bcrypt hashes are 60 chars)

Example (Node.js + PostgreSQL):

const query = 'INSERT INTO users (username, password_hash) VALUES ($1, $2)';
const values = [username, bcryptHash];

await pool.query(query, values);

Best Practices to Keep in Mind

Here's where people often mess up. Let’s make sure you're ticking the right boxes.

✅ Must-do List:

  1. Always salt and hash passwords
  2. Use strong work factors (12 or more) depending on server resources
  3. Never decode or decrypt a hashed password
  4. Treat passwords like secrets in transit (TLS, HTTPS)
  5. Store and access hashes with care - use role-based access control
  6. Use rate limiting, account locking in case of repeated incorrect attempts
  7. Enforce strong password policies (min 12 chars, complexity)

Alternatives to bcrypt: When and Why

Sure, bcrypt is great. But how does it stack up against the rest? Check this out:

Algorithm Salting Built-in Adjustable Parameters Memory-Hard Best For
bcrypt ✅ Yes Cost (work factor) ❌ No General-purpose sites
Argon2 ✅ Yes Time, memory, threads ✅ Yes High-security modern apps
PBKDF2 ❌ No Iterations ❌ No Enterprise & compliance (FIPS)

TL;DR:

  • Use bcrypt for most web apps today
  • For bleeding-edge security (or storing crypto keys), look into Argon2
  • If working in legacy or regulated contexts, PBKDF2 could still be required

Updating Your Cost Factor Over Time

The bcrypt cost factor (like 12) decides how many rounds the algorithm runs. Each increase doubles computation time.

Cost (Rounds) Approx Time (on modern CPUs)
10 ~60ms
12 ~250ms
14 ~1s

As hardware gets faster, plan to increase the cost. If you rehash on user password changes or logins, it's a good opportunity.


Dealing with Password Changes

When users update their passwords:

  1. Don’t reuse the old salt
  2. Generate a new hash with a fresh cost and salt
  3. Replace the stored hash entirely

There’s no need to verify the old password unless doing so for extra security. Just validate and overwrite.


Real-World Example: Login Flow

Here’s how a secure login system would work:

  1. User signs up
    → Password is hashed with bcrypt
    → Store username and hash in DB

  2. User logs in
    → Compare bcrypt.compare(input, stored_hash)
    → If true, authenticate user; else, deny access

You don’t decrypt anything. All trust is placed in the one-way hash and bcrypt doing its job.


Mistakes to Avoid

Avoiding pitfalls is as important as doing the right thing.

❌ Common Missteps:

  • Storing passwords in plaintext or weak hashes (like MD5)
  • Forgetting to salt passwords
  • Using same salt for all users
  • Using a cost that's too low (below 10 is outdated)
  • Exposing hashes in responses or logs
  • Trying to reverse a hash (it’s not encryption)

Roundup: Why bcrypt, Why Now

bcrypt has been battle-tested across web platforms and languages for decades. If you're looking to build a secure, scalable authentication system, it's hard to go wrong with this algorithm.

Of course, stay updated. Password storage is one small part of the full authentication puzzle—factor in two-factor auth, HTTPS, infrastructure security, and good user hygiene too.



Key Resources and References


Want to build apps that don’t just work, but work securely? Bookmark this guide. Password handling isn't just an implementation detail—it’s a critical frontline defense.

Keep coding safe out there.

Frequently Asked Questions