All posts

Field guide

Weak password hashing in AI code: why MD5 and SHA are not enough

Ask an assistant to store a password and there is a good chance it reaches for MD5 or SHA-256. Both produce working login code, and both are the wrong tool, because they are fast, and fast is exactly what you do not want protecting a password. Here is why, and the one-line switch that fixes it.

Last updated

Storing passwords is one of those tasks where the code can look completely correct and still be wrong. An assistant writes a hash, the login works, the test passes. But the choice of hash decides whether a database leak is a minor incident or a full dump of your users' passwords, and the common AI choices, MD5 and SHA-256, land on the wrong side of that line.

Why fast hashing is the problem

The danger only appears after a breach. Once an attacker has your stored hashes, they guess passwords offline, hashing candidates and comparing. With a fast hash like MD5 or SHA-256 they can try billions of guesses per second on ordinary hardware, so weak and reused passwords, which make up a large share of any user base, fall almost immediately. A password hash is supposed to be deliberately slow, so each guess costs real time and mass guessing stops being practical.

// the weak pattern: a fast general-purpose hash
const hash = crypto
  .createHash("md5")     // or "sha256" — same problem
  .update(password)
  .digest("hex");

There is also often no salt, which means identical passwords produce identical hashes and precomputed tables crack them instantly. The concept in full is in what password hashing is.

Want to see exactly what Prbl flags? Watch it scan a demo app, no repo or account needed.

See a live scan →

The fix: a slow, salted, purpose-built hash

Use bcrypt, scrypt, or argon2. They are built to be expensive to compute, they generate and store a per-password salt for you, and they let you raise the cost over time as hardware speeds up. bcrypt with a reasonable work factor is a dependable default.

import bcrypt from "bcrypt";

// store this hash; the salt is included in it
const hash = await bcrypt.hash(password, 12);

// verify on login
const ok = await bcrypt.compare(password, hash);

Same amount of code, entirely different resistance after a breach. The full walkthrough is in the fix for weak password hashing.

Migrating an existing store

You cannot un-hash the old passwords, so upgrade them on next login: when a user signs in and their password verifies against the old hash, re-hash it with bcrypt and save that. Active users migrate themselves over time, and you can expire or force a reset for accounts that never return. Until the switch is done, treat the current store as weak.

Frequently asked questions

Why is a fast hash like SHA-256 bad for passwords?

Because speed is what helps the attacker here. If your database leaks, an attacker runs guesses against the stolen hashes offline, and a fast hash lets them try billions per second on ordinary hardware, so common and reused passwords fall almost immediately. A password hash is supposed to be slow on purpose, so that each guess costs real time and large-scale guessing becomes impractical. SHA-256 is a great general hash and a poor password hash for exactly this reason.

What should I use instead?

Use a slow, salted, purpose-built password hashing algorithm: bcrypt, scrypt, or argon2. They are designed to be expensive to compute, they include a per-password salt automatically, and they let you tune the cost as hardware gets faster. bcrypt with a sensible work factor is a solid default and available everywhere; argon2 is the modern choice if your platform supports it.

What is a salt and do I need to add one myself?

A salt is a unique random value mixed into each password before hashing, so that two users with the same password get different hashes and precomputed lookup tables do not work. With bcrypt, scrypt, and argon2 the salt is generated and stored for you as part of the output, so you do not manage it separately. That is another reason to use them rather than assembling your own scheme with a general hash.

I already stored passwords with MD5 or SHA. How do I migrate?

You cannot reverse the old hashes, so you upgrade on next login: when a user signs in and the password verifies against the old hash, re-hash it with the new algorithm and store that. Over time active users migrate automatically. For accounts that never return, expire the old hashes or force a reset. In the meantime, treat the existing store as weak and prioritize the switch.

Check how your app hashes passwords

A fast hash on a password field has a recognizable shape, and a scan flags it with the file and line, so you can switch to bcrypt before a leak turns your hashes into plaintext. Run a free scan and see how your passwords are stored.

Prbl scans your live app or your codebase for exactly the kinds of issues above.

Weak Password Hashing in AI Code: Why MD5 and SHA Are Not Enough: Prbl