Information Security 34 min read

Proper Password Hashing: Salting, Key Stretching, and Secure Implementation

This article explains why simple password hashing is insufficient, describes common attacks such as dictionary, brute‑force, lookup‑table and rainbow‑table attacks, and provides best‑practice guidance—including random salts, CSPRNGs, key‑stretching algorithms like PBKDF2, bcrypt and scrypt—and complete PHP reference implementations.

Architect
Architect
Architect
Proper Password Hashing: Salting, Key Stretching, and Secure Implementation

0x00 Background

Most web developers need to design a user‑account system, and protecting passwords is the most critical part. Data breaches of large companies show that storing plain passwords is dangerous, so salted password hashing must be used.

0x01 Important Reminder

Do not write your own password‑hashing code; it is easy to get wrong. Use proven libraries such as phpass or the source code provided in this article.

0x02 What Is a Hash

A hash function is a one‑way function that converts arbitrary data into a fixed‑length fingerprint. Small changes in input produce large changes in output, making hashes suitable for password storage.

The typical registration/authentication flow is:

1. User creates an account.
2. Password is hashed (with a salt) and stored; no plaintext password is saved.
3. On login, the entered password is hashed with the same salt and compared to the stored hash.
4. If the hashes match, authentication succeeds.
5. Repeat step 3 for each login attempt.

Never reveal whether the username or password was incorrect; use a generic error message to prevent user‑enumeration attacks.

Only cryptographic hash functions (e.g., SHA‑256, SHA‑512, RIPEMD, WHIRLPOOL) are suitable for password hashing; general‑purpose hash tables are not secure enough.

0x03 How Hashes Are Cracked

Dictionary and Brute‑Force Attacks

Attackers guess passwords, hash each guess, and compare to the target hash. Dictionary attacks use a list of common passwords; brute‑force tries every possible character combination.

Lookup Tables

Pre‑compute hashes for a large password dictionary and store them in a table for fast lookup. This is effective against unsalted hashes.

Reverse Lookup Tables

Attackers build a table of usernames and their hashes, then hash a common dictionary and compare, allowing them to discover which users reuse passwords.

Rainbow Tables

Rainbow tables trade computation time for storage space, enabling the cracking of many unsalted hashes with relatively small tables.

0x04 Adding Salt

Salting adds a random prefix or suffix to each password before hashing, ensuring that identical passwords produce different hashes and rendering lookup‑table attacks ineffective.

The salt does not need to be secret; it must be random and stored alongside the hash.

0x05 Incorrect Practices: Short Salt and Salt Reuse

Reusing the same salt for many passwords or using a short salt makes attacks feasible. Always generate a new, sufficiently long random salt for each password (e.g., 32 bytes for SHA‑256).

0x06 Incorrect Practices: Double‑Hashing and Exotic Hash Functions

Combining multiple hash functions or using non‑standard algorithms provides little security benefit and can introduce compatibility and safety issues. Use well‑tested standard algorithms instead.

0x07 Hash Collisions

Cryptographic hash functions are designed to make collisions computationally infeasible, but weak hashes like MD5 have known practical collisions. Prefer strong hashes such as SHA‑256, SHA‑512, RIPEMD, WHIRLPOOL, or SHA‑3.

0x08 Correct Approach: Proper Password Hashing

Basic: Salted Hash

Generate a salt with a cryptographically secure pseudo‑random number generator (CSPRNG) and hash the password+salt using a strong algorithm.

Platform

CSPRNG

PHP

mcrypt_create_iv, openssl_random_pseudo_bytes

Java

java.security.SecureRandom

.NET (C#, VB)

System.Security.Cryptography.RNGCryptoServiceProvider

Ruby

SecureRandom

Python

os.urandom

Perl

Math::Random::Secure

C/C++ (Windows API)

CryptGenRandom

Linux/Unix

/dev/random or /dev/urandom

Store both the salt and the resulting hash in the user record.

Storing a password:

1. Generate a long random salt using a CSPRNG.
2. Concatenate password and salt, then hash with a standard algorithm (e.g., SHA‑256).
3. Store the salt and hash in the database.

Verifying a password:

1. Retrieve the stored salt and hash.
2. Concatenate the entered password with the same salt and hash.
3. Compare the computed hash with the stored hash; if equal, authentication succeeds.

Key Stretching (Slow Hashes)

To defend against fast dictionary/brute‑force attacks, use a deliberately slow hash function such as PBKDF2, bcrypt, or scrypt, which performs many iterations and consumes CPU resources.

Typical parameters are chosen so that a single hash takes about half a second on the target hardware.

When using slow hashes in a web application, consider the impact on server load and possible denial‑of‑service attacks; you may lower the iteration count or add CAPTCHAs for suspicious login attempts.

0x09 Other Security Measures

Beyond hashing, protect the entire application: follow OWASP Top Ten, conduct regular security training, perform third‑party penetration testing, and monitor for intrusions.

0x0A Frequently Asked Questions

Which hash algorithms should I use?

PBKDF2, bcrypt, scrypt (key‑stretching)

Secure cryptographic hashes: SHA‑256, SHA‑512, RIPEMD, WHIRLPOOL, SHA‑3

Avoid outdated hashes like MD5 and SHA‑1, and avoid insecure crypt() variants.

How to handle password resets? Use a one‑time, random token bound to the user account, expire it quickly (e.g., 15 minutes), and never email plaintext passwords.

What if the user database is leaked? Notify users promptly, advise them to change passwords, and consider additional verification steps such as email confirmation.

0x0B PHP PBKDF2 Password‑Hashing Code

Below is a complete PHP implementation of PBKDF2‑based password hashing, including constant‑time comparison to mitigate timing attacks.

The slow_equals function uses XOR (^) to compare bytes without branching, preventing timing attacks.

By following the guidelines and using the provided reference implementation, developers can store passwords securely and mitigate common attack vectors.

securityPHPsaltingcryptographyPBKDF2password hashingkey stretching
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.