SQL Injection Prevention: Escaping vs Parameterization
Learn the critical differences between escaping and parameterized queries for SQL injection prevention. Understand why parameterization is the gold standard for database security.
Learn why password hashing matters, why MD5 is broken for security, how SHA-256 differs, and why bcrypt and Argon2 are the right choice for storing passwords. Understand rainbow tables, salting, and modern best practices.
Years ago, I inherited a project where passwords were stored in plain text. PLAIN TEXT. When I saw the users table, my stomach dropped. Thousands of passwords, just sitting there readable by anyone with database access. I spent the next weekend migrating to bcrypt and praying we hadn't been compromised yet.
Hashing is not encryption - this distinction matters. Encryption is reversible with a key. Hashing is intentionally one-way. You verify passwords by hashing the input and comparing, but you can never recover the original. This is exactly what we want for password storage.
A hash function takes any input and produces fixed-length output. The same input always gives the same output, but change one character and the result is completely different. We call this the avalanche effect.
I still find MD5 password hashes in legacy codebases. Every time, I have the same conversation: MD5 is broken. It was created in 1991 and is completely unsuitable for security in 2024.
SHA-1 produces a 160-bit hash and was the standard from 1995 until Google's SHAttered attack in 2017. They created two different PDFs with the same SHA-1 hash. Game over.
SHA-2 is a family of hash functions from NIST (2001). SHA-256 and SHA-512 are the ones you'll actually use. No practical attacks exist against them.
SHA-256 is cryptographically solid. But here's the problem: it's WAY too fast. A single GPU can compute over 10 billion SHA-256 hashes per second. That's a problem when you're trying to slow down attackers.
Rainbow tables are precomputed lookup tables mapping hashes to original inputs. Instead of computing during an attack, you just look up the hash. Unsalted password databases become trivial to crack.
A salt is random data added to each password before hashing. Identical passwords get different hashes. Rainbow tables become useless. Salts don't need to be secret - they just need to be unique per user.
bcrypt was designed for password hashing back in 1999 and it's still excellent. Built-in salt, configurable work factor, widely supported. This is my default choice.
Argon2 won the Password Hashing Competition in 2015. If I'm starting a new project and have the choice, I'll use Argon2id. Configurable memory, time, and parallelism - it's designed to resist both GPU and ASIC attacks.
scrypt came out in 2009, designed to be memory-hard. Litecoin uses it. It's between bcrypt and Argon2 in features and adoption.
Here's my decision tree for password hashing:
Getting the algorithm right is only half the battle. Implementation details matter just as much.
Every major language has solid password hashing libraries. Use them.
Inherited an MD5 or unsalted SHA codebase? Here's how I handle migrations:
Password hashing seems simple. It's not. I catch these mistakes in code reviews all the time:
While this post is about passwords, hash functions are everywhere in my work:
Password hashing protects your users when (not if) your database gets breached. Use Argon2id for new projects, bcrypt for existing systems. Never MD5, never SHA-1, never plain SHA-256.
Hashing is not encryption. Salts must be unique, not secret. Work factors need regular review. Play with the Hash Generator tool on CodeUtil to see how these algorithms differ, but always use proper password hashing libraries in production code.
Hashing is one-way: you cannot recover the original data from a hash. Encryption is two-way: with the correct key, you can decrypt data back to its original form. Passwords should be hashed (not encrypted) because you never need to retrieve the original password—only verify that a user knows it.
MD5 has known collision vulnerabilities that allow attackers to create different inputs with the same hash. More importantly for passwords, MD5 is extremely fast (billions of hashes per second on GPUs), enabling rapid brute-force attacks. Rainbow tables for MD5 are widely available and cover most common passwords.
A salt is a random value added to each password before hashing. It ensures identical passwords produce different hashes, defeating precomputed rainbow tables and preventing attackers from identifying users with the same password. Salts must be unique per user but do not need to be secret.
For new applications, use Argon2id—it won the Password Hashing Competition and provides the strongest security with configurable memory, time, and parallelism costs. For existing applications already using bcrypt with adequate work factors (12+), there is no urgent need to migrate. Both are secure when properly configured.
Password hashing should take between 100 and 500 milliseconds on your production hardware. This is slow enough to prevent brute-force attacks but fast enough not to impact user experience. Adjust the work factor (iterations, memory) to achieve this target, and retest periodically as hardware improves.
Plain SHA-256 is not suitable for password hashing because it is too fast. A GPU can compute billions of SHA-256 hashes per second, making brute-force attacks practical. If you must use SHA-256, apply it through PBKDF2 with at least 310,000 iterations (OWASP 2023 recommendation), but bcrypt or Argon2 are better choices.
With properly hashed passwords (bcrypt/Argon2 with adequate work factors), attackers cannot easily recover the original passwords. They would need to attempt brute-force attacks, which are computationally expensive. However, users with weak passwords are still at risk, which is why password policies and breach notification matter.
The safest migration is transparent rehashing: when a user logs in, verify their password against the old MD5 hash, then immediately rehash it with bcrypt or Argon2 and store the new hash. For inactive accounts, consider requiring password resets. Never try to decrypt or reverse existing hashes—this is mathematically impossible.
Founder of CodeUtil. Web developer building tools I actually use. When I'm not coding, I experiment with productivity techniques (with mixed success).
Learn the critical differences between escaping and parameterized queries for SQL injection prevention. Understand why parameterization is the gold standard for database security.
Learn how JWT claims work, explore registered, public, and private claims, and discover security best practices for implementing JSON Web Tokens in your applications.
Understand JSON Web Tokens (JWT) from the ground up: how they work, their three-part structure, when to use them, security best practices, refresh token strategies, and common implementation mistakes to avoid.