Hashes and Salt for Passwords in PHP: Security Guide 2025

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Passwords in the Crosshairs: Why Old Methods No Longer Work

\n

Imagine: you are a developer writing a registration system. It seems simple, right? Take a password, run it through md5, save it to the database. But in 2025, this approach is a direct path to vulnerability. Modern attacks, GPU brute-forcing, and rainbow tables turn weak hashes into dust in seconds. The question from Stack Overflow with thousands of views — "Secure hash and salt for PHP passwords" — remains relevant, and we will settle it once and for all.

\n

In this article, you will learn how to properly hash passwords in PHP using modern algorithms, built-in functions, and salt. We adapt the best community answers for educational content and show you how to protect users even in 2025.

\n\n

Why md5 and sha1 are Evil

\n

If you are still using md5 or sha1 to store passwords — stop. These algorithms were designed for speed, not security. In 2025, a GPU can brute-force billions of md5 hashes per second. Adding salt (a random string) does not help: rainbow tables are bypassed, but the speed remains fatal.

\n
    \n
  • Md5: 2^128 variants — cracked in minutes.
  • \n
  • Sha1: slightly better, but also outdated.
  • \n
  • Salt without slowing down: if the hash is fast, salt only delays the attack.
  • \n
\n

Instead, you need algorithms with "slowness" — those that require time and memory. This is exactly what password_hash uses.

\n\n

password_hash and password_verify: Built-in Solution from PHP

\n

Since PHP 5.5, two functions have solved 90% of password security problems: password_hash and password_verify. They automatically generate a cryptographically secure salt and use algorithms resistant to brute-force. In 2025, this is the standard, and no homemade solutions!

\n\n

How Does It Work?

\n

The password_hash function takes a password and an algorithm (default is bcrypt, but you can specify argon2). It returns a string that already contains the salt and parameters. You do not need to store the salt separately — it is inside the hash.

\n
<?php\n// Hash the password\n$password = "SuperSecret123!";\n$hash = password_hash($password, PASSWORD_DEFAULT);\n\necho $hash; // $2y$10$... (60 characters)\n?>\n
\n

Now let's verify the password during login:

\n
<?php\n$inputPassword = "SuperSecret123!";\n$storedHash = "$2y$10$..."; // from the database\n\nif (password_verify($inputPassword, $storedHash)) {\n    echo "Password is correct!";\n} else {\n    echo "Invalid password.";\n}\n?>\n
\n

It's ingeniously simple. password_verify automatically extracts the salt from the hash and checks the password. No additional steps needed.

\n\n

Algorithm Choice: bcrypt vs argon2

\n

In 2025, you have two main options:

\n
    \n
  • Bcrypt (PASSWORD_BCRYPT): time-tested, used by default. Resistant to attacks, but requires configuring the cost factor.
  • \n
  • Argon2 (PASSWORD_ARGON2I or PASSWORD_ARGON2ID): winner of the Password Hashing Competition. More modern, requires more memory, making GPU attacks harder. Recommended for new projects.
  • \n
\n

Example with argon2:

\n
<?php\n$hash = password_hash($password, PASSWORD_ARGON2ID, [\n    'memory_cost' => 1 << 17, // 128 MB\n    'time_cost'   => 4,\n    'threads'     => 2\n]);\n?>\n
\n

The memory_cost parameter is the amount of memory in kilobytes. The higher it is, the more secure, but slower. Test on your server for production.

\n\n

Salt: How It Works and Why You Don't Need It Separately

\n

Many beginners ask: "How do I add salt manually?" The answer: don't. password_hash generates 22 characters of random salt (for bcrypt

Blogs

Book Recommendations