Cyber Security Password Hashing for Developers — Why bcrypt, Argon2, and scrypt are Safer than MD5 Written by Adam Muiz 23 Jul 2026 Updated: 06 Aug 2026 9 min read Password Hashing for Developers — Why bcrypt, Argon2, and scrypt are Safer than MD5I still remember the days when I first created a login system. At that time, I saved the user password directly as plain text in the MySQL database. It feels practical: login just compare password_input == password_database, done. Only after reading an article about data leaks did I realize that this practice was like keeping house keys under the doormat in front of the door. It looks easy, but as soon as someone lifts the mat, all the doors open.From there I started learning about password hashing. It turns out that storing passwords is not about hiding the text, but rather turning it into a representation that cannot be reversed — not even by the system owner. This article is a record of my journey in understanding bcrypt, Argon2, scrypt, and why old algorithms like MD5 are no longer suitable for use.Hashing Is Not Encryption, and That's ImportantMany beginners think that hashing and encryption are the same. Even though the two are different, like a mailbox and a safe box. Encryption is a safe box that can be opened again with a key. Hashing is a mailbox where letters can only be entered, but cannot be retrieved. The result is that once you enter, there is no way out.When we hash a password, we don't want to be able to return it to the original text. All we need is the ability to verify: does the password the user types when logging in produce the same hash as the one stored? If yes, access is accepted. If not, reject.This concept is called one-way function. Functions that are easy to execute in one direction, but very difficult to reverse. Think of it like making a smoothie: it's easy to blend fruit, but almost impossible to separate it back into whole apples, bananas and mangoes.Why are MD5 and SHA1 No Longer Enough?In the past, MD5 was king. All early PHP tutorials used md5($password). But over time, MD5 showed a fatal flaw: it was too fast. Speed is usually an advantage in computing, but in the context of password hashing, speed actually backfires.The faster an algorithm generates a hash, the faster an attacker can try billions of password combinations in one second. This technique is called brute-force or dictionary attack. With a modern GPU, a single computer can try trillions of MD5 hashes per second. Passwords like "P@ssw0rd123" can be cracked in minutes.In addition, MD5 is also susceptible to collision: two different inputs can produce the same hash. Even though the collision does not directly reveal the password, it shows that the MD5 structure is no longer solid. Just like the foundation of a cracked house — it may still be standing, but it's not livable.SHA1 has a similar problem. Both are designed for speed, not password security. They are great for file checksums, but are misplaced if used to store credentials.Salt: Let Every Hash Be UniqueBefore getting into modern algorithms, there is one important concept: salt. Imagine you are making instant noodles. If everyone uses the same spices, the results will be the same. But if each person adds a little different secret ingredient — a spoonful of soy sauce, a pinch of chili, half a spoonful of sesame oil — then the same noodles taste different. That's saltIn hashing, a salt is a random string that is added to a password before it is hashed. The salt is stored along with the hash in the database. So even if two users have the password "123456", their hashes will be different because their respective salts are different. This destroys the efficiency of the rainbow table, which is a precomputed hash table for common passwords.Without salt, an attacker can match the leaked hash with a rainbow table and immediately find the password. With salt, they have to attack each hash one by one, slowing down the process drastically. But salt alone is not enough if the basic algorithm is still fast like MD5.bcrypt: An Old Standard That's Still Trustedbcrypt is a password hashing algorithm specifically designed for slowdown. It combines salts internally, so developers don't have to manage salts manually. The most interesting thing: bcrypt has a cost factor parameter that determines how many iterations are performed. The cost factor can be increased as the hardware speed increases.In PHP, bcrypt is used automatically via the password_hash() and password_verify() functions. This is one of the best APIs I've ever encountered: simple, secure, and infallible and easy to use. 12]); // $hash berisi salt + cost + hash dalam satu string // Saat login if (password_verify($password, $hash)) { // Login berhasil } // Upgrade cost factor jika perlu if (password_needs_rehash($hash, PASSWORD_BCRYPT, ['cost' => 13])) { $hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]); } ?> Common cost values currently range from 10 to 13. The higher, the slower the hashing process. Usually we choose a cost factor that makes the hashing process take around 250-500 milliseconds. That's slow enough to hinder brute-force, but still convenient for a single logged in user.Argon2: Password Hashing Competition ChampionArgon2 was the winner of the 2015 Password Hashing Competition. It was designed to be more resilient to attacks with specialized hardware such as GPUs and ASICs. Argon2 has three variants: Argon2d, Argon2i, and Argon2id. For passwords, the most recommended is Argon2id, because it offers the best protection from various types of attacks.What makes Argon2 special is the use of memory-hard. It's not just the CPU that is burdened, but also the memory. This makes attacks with GPUs much more expensive because GPUs usually have limited memory per thread. An analogy: if bcrypt makes attackers walk, Argon2 makes them walk while carrying large rocks on their backs.In PHP 7.2+, Argon2 is available as the default option for password_hash(). The configuration is a little more complex than bcrypt because there are three parameters: memory cost, time cost, and threads. 65536, // 64 MB 'time_cost' => 4, // iterasi 'threads' => 3 // parallelism ]); // Verifikasi tetap sama if (password_verify($password, $hash)) { // Login berhasil } ?> The parameters above can be adjusted according to server capacity. It is important not to choose a value that is too low, as it will reduce protection against hardware attacks. On the other hand, a value that is too high can overwhelm the server if there are many simultaneous logins.scrypt: A Reliable Memory-Hard Alternativescrypt is a memory-hard algorithm designed by Colin Percival. The main goal is the same as Argon2: to make brute-force with dedicated hardware inefficient. scrypt was widely used in the cryptocurrency world, including Litecoin, before being overtaken by Argon2.Even though scrypt didn't win the competition, it is still considered safe and is used in some systems. The difference with bcrypt: bcrypt is more limited in CPU costs, while scrypt also adds memory costs. If bcrypt is like locking a door with lots of keys, scrypt locks the door with lots of keys and makes the room behind it narrow so it's hard to move.In PHP, scrypt is not available natively via password_hash(). If you want to use it, you usually need additional extensions or third-party libraries. For most modern web applications, bcrypt or Argon2 is more than sufficient.Pepper: An Additional Layer of SecretsBesides salt, there is a concept that is less frequently discussed: pepper. If salt is a unique ingredient stored with the hash, then pepper is a global secret stored separately from the database. Usually pepper is stored in the server configuration file or environment variables, not in the database.The function of pepper is to provide an additional layer of protection if the database is leaked. An attacker who only gets a database dump will not be able to verify the password without knowing the pepper. It's like having a safe at home with two keys: one key is kept in the safe itself, the other is always in your pocket.However, pepper should be used with caution. If pepper is lost or changed, all passwords become unverifiable. It is not a substitute for salt, but a complement. The usual flow is: combine password + salt, hash, then combine the results with pepper, hash again.Best Practices that I ApplyAfter understanding various algorithms, I concluded some basic practices that I always try to apply: Never store passwords in plaintext or reversible encryption.Use password_hash() and password_verify() in PHP; do not create your own hashing algorithm.Choose Argon2id if available, fallback to bcrypt with a high cost factor.Use a unique and automatic salt for each password.Implement strong password policies on both the client and server sides, but remember that policies alone do not replace good hashing.Consider rate limiting and account lockout to inhibit brute-force.Prepare an incident response plan if the database is leaked, including forcing users to change passwords. Most important of all: never feel like security is a one-and-done task. Security is a process. An algorithm that is considered safe today may no longer be safe tomorrow. That's why functions like password_needs_rehash() are so useful: they allow us to increase security gradually without disturbing the user.ConclusionPassword hashing is one of the foundations of application security that is often underestimated. In fact, one mistake here can have fatal consequences when data is leaked. MD5 and SHA1 may still be seen in many old tutorials, but they are not the right choice for storing passwords. Use bcrypt, Argon2id, or scrypt — algorithms designed for slowdown and resistance to modern attacks.For me, understanding password hashing is like learning to lock your house properly. A good key doesn't have to be complicated, but it has to be correct. And most importantly, we must know why the key was chosen.If you have other experiences or opinions about password hashing, please leave them in the comments column. I enjoy learning from different viewpoints. If this article is useful, don't hesitate to share it.