Question
Secure Password Hashing in PHP: Salts, password_hash(), and Best Practices
Question
I understand that MD5 is no longer considered safe for password storage. Given that, what mechanism should I use to protect passwords in PHP?
I have seen conflicting advice:
- Some discussions suggest hashing a password multiple times.
- Others recommend using a salt.
I want to build a password storage system in PHP that is both secure and reasonably fast. A very large number of hash iterations may improve security, but it also makes authentication slower. How do I choose a good balance between speed and security?
I also prefer the stored result to have a consistent format or length if possible.
My requirements are:
- The hashing mechanism must be available in PHP.
- It must be secure for password storage.
- It may use a salt, but if so:
- Are all salts equally good?
- How should a good salt be generated?
I also want to know whether storing multiple hashes for the same password, such as one MD5 hash and one SHA hash, would make the system more secure or less secure.
In short, I want to know:
- Which password hashing function(s) should be used in PHP
- Whether salting is necessary
- How to generate a good salt
- Whether multiple different hashes of the same password improve security
Short Answer
By the end of this page, you will understand how passwords should be stored securely in PHP, why MD5 and plain SHA hashes are not suitable, how salts work, why modern password hashing functions are designed to be slow on purpose, and how to use password_hash() and password_verify() correctly in real applications.
Concept
What problem are we solving?
When a user creates a password, you should not store the original password in your database. Instead, you store a password hash.
A hash is a one-way transformation:
- You can hash a password and store the result.
- Later, when the user logs in, you hash the entered password again and compare it.
- You should not be able to reverse the hash back into the original password.
Why MD5 and SHA are not enough
Functions like MD5, SHA1, and even SHA256 are fast general-purpose hashing algorithms. That is good for checksums and file integrity, but bad for password storage.
Why bad?
- Attackers can test huge numbers of guesses very quickly.
- Modern GPUs and specialized hardware can crack fast hashes efficiently.
- Precomputed lookup tables and large wordlists make attacks easier.
Password hashing should be slow by design.
What makes a password hash secure?
A secure password storage system should have these properties:
- One-way: the original password is not stored.
- Slow to compute: brute-force attacks become expensive.
- Salted: each password gets its own random salt.
- Upgradeable: you can increase the cost over time.
What is a salt?
A salt is a random value added to a password before hashing.
This means:
Mental Model
Think of password hashing like storing a locked box label instead of the key itself.
- The password is the key.
- The hash is a label created from that key.
- The salt is like adding a unique serial number before making the label.
If two people have the same key, the serial number makes their labels different.
A fast hash like MD5 is like a machine that prints labels instantly. That sounds useful, but it also helps attackers print billions of labels while guessing passwords.
A password hashing algorithm like bcrypt or Argon2 is like a machine intentionally built to work more slowly and carefully. That delay is small for a real login, but expensive for an attacker trying millions of guesses.
Syntax and Examples
Recommended PHP syntax
Hashing a password
<?php
$password = 'MySecureP@ssw0rd!';
$hash = password_hash($password, PASSWORD_DEFAULT);
echo $hash;
Verifying a password
<?php
$enteredPassword = 'MySecureP@ssw0rd!';
$storedHash = '$2y$10$exampleexampleexampleexampleexampleexampleexampleexam';
if (password_verify($enteredPassword, $storedHash)) {
echo 'Password is correct';
} else {
echo 'Invalid password';
}
Better example with real flow
Registering a user
<?php
$password = $_POST['password'];
$hash = password_hash($password, PASSWORD_DEFAULT);
// Store $hash in the database
Step by Step Execution
Example
<?php
$password = 'cat123';
$hash = password_hash($password, PASSWORD_DEFAULT);
$isValid = password_verify('cat123', $hash);
var_dump($isValid);
What happens step by step
1. The password is defined
$password = 'cat123';
The variable holds the plain-text password.
2. PHP creates a secure password hash
$hash = password_hash($password, PASSWORD_DEFAULT);
PHP does several things internally:
- Chooses the current default password hashing algorithm
- Generates a secure random salt
- Combines the password and salt correctly
- Applies the algorithm with the proper cost settings
- Returns a full hash string containing the metadata needed later
3. The password is checked
= (, );
Real World Use Cases
User authentication
The most common use case is login systems:
- registration forms
- sign-in pages
- admin dashboards
- membership systems
API and developer portals
If your platform has user accounts for accessing API keys or dashboards, the passwords for those accounts should be stored with password_hash().
Internal business tools
Even small internal apps need proper password storage:
- employee portals
- HR dashboards
- reporting systems
- support tools
Password resets and account migration
During a login, you can verify an old stored hash and rehash it using stronger settings. This is useful when improving security in existing apps.
Multi-tenant SaaS applications
Different users may choose the same password. Salted password hashing prevents them from ending up with identical stored hashes, which reduces information leakage if the database is exposed.
Real Codebase Usage
Typical pattern in real projects
Developers usually build password hashing into these layers:
- registration service
- login/authentication service
- user model or repository
- password reset flow
Common patterns
Validation before hashing
<?php
if (strlen($password) < 12) {
throw new InvalidArgumentException('Password must be at least 12 characters.');
}
$hash = password_hash($password, PASSWORD_DEFAULT);
Passwords should be validated before hashing, not after.
Guard clause for failed login
<?php
if (!$user) {
return false;
}
if (!password_verify($password, $user['password_hash'])) {
return false;
}
return true;
This keeps authentication logic simple and readable.
Common Mistakes
1. Using MD5 or SHA directly for passwords
Broken approach:
<?php
$hash = md5($password);
Or:
<?php
$hash = hash('sha256', $password);
Why it is a problem:
- They are too fast.
- They are not designed for password storage.
- Attackers can brute-force them efficiently.
Use this instead:
<?php
$hash = password_hash($password, PASSWORD_DEFAULT);
2. Making your own salt incorrectly
Broken approach:
<?php
$salt = '12345';
$hash = hash('sha256', $salt . $password);
Problems:
- Salt is predictable.
Comparisons
Password hashing choices in PHP
| Option | Suitable for passwords? | Speed | Salt handling | Recommended? | Notes |
|---|---|---|---|---|---|
md5() | No | Very fast | Manual | No | Obsolete for password storage |
sha1() | No | Very fast | Manual | No | Also too fast |
hash('sha256', ...) | No | Fast | Manual | No | Better than MD5 for integrity, not for passwords |
Cheat Sheet
Best practice
$hash = password_hash($password, PASSWORD_DEFAULT);
$isValid = password_verify($password, $hash);
Rules to remember
- Do not use
md5()for passwords. - Do not use
sha1()orsha256directly for passwords. - Do not store plain-text passwords.
- Do not build your own salt system unless you truly need to.
- Do use
password_hash(). - Do use
password_verify(). - Do use
password_needs_rehash()over time. - Do give your database column enough space, such as
VARCHAR(255).
Good salt rules
- random
- unique per password
- generated with a secure source
- usually handled automatically by
password_hash()
Good database field
password_hash VARCHAR(255)
FAQ
Should I use MD5 for passwords in PHP?
No. MD5 is too fast and no longer suitable for password storage.
Is SHA-256 safe for password hashing?
Not by itself for password storage. It is a strong general-purpose hash, but it is too fast for defending against brute-force attacks.
Do I need to generate my own salt in PHP?
Usually no. password_hash() generates and stores a secure salt automatically.
Is bcrypt still acceptable in PHP?
Yes. Bcrypt is still a solid choice and is widely supported. Argon2id is also an excellent option when available.
Why are password hashes intentionally slow?
Because slow hashing makes brute-force attacks more expensive while keeping normal login checks practical.
Should I store several hashes of the same password for extra security?
No. That usually weakens security because an attacker can target the easiest hash.
How long should the database column be for a password hash?
A common practical choice is VARCHAR(255) so future algorithm changes are less likely to cause problems.
Is password hashing the same as encryption?
No. Hashing is one-way and is used for password verification. Encryption is two-way and is used when data must be recovered later.
Mini Project
Description
Build a small PHP password registration and login example that stores a secure password hash and verifies it during login. This demonstrates the correct use of PHP's built-in password hashing API without using MD5, SHA1, or custom salts.
Goal
Create a simple PHP script that can hash a new password, store the hash in memory, and verify login attempts against it securely.
Requirements
- Accept a plain-text password and create a secure hash using PHP.
- Store the generated hash in a variable that simulates a database value.
- Verify one correct login attempt and one incorrect login attempt.
- Show how to detect whether the stored hash should be rehashed.
- Do not use MD5, SHA1, or manual salt generation.
Keep learning
Related questions
Converting HTML and CSS to PDF in PHP: Core Concepts, Limits, and Practical Approaches
Learn how HTML-to-PDF conversion works in PHP, why CSS support varies, and how to choose practical approaches for reliable PDF output.
How PHP foreach Actually Works with Arrays
Learn how PHP foreach works internally, including array copies, internal pointers, by-value vs by-reference behavior, and common pitfalls.
How to Check String Prefixes and Suffixes in PHP
Learn how to check whether a string starts or ends with specific text in PHP using simple functions and practical examples.