A lot of data-storage mistakes come down to confusing two tools that solve opposite problems. Encryption is reversible, so you can get the original value back with the key. Hashing is one-way, so you cannot. Once you know which property each has, the right choice for any given piece of data is almost automatic. Here is the rule, in three cases.
Passwords: hash them
You never need to read a password back; you only need to check whether a login attempt matches. That is what hashing is for. Encrypting a password is a mistake, because a leak of the encryption key turns into a leak of every password. Use a slow, salted, purpose-built hash like bcrypt:
import bcrypt from "bcrypt"; const hash = await bcrypt.hash(password, 12); // store this const ok = await bcrypt.compare(attempt, hash); // verify on login
Never a fast general hash like MD5 or SHA-256 on its own; the reasoning is in weak password hashing.
Want to see exactly what Prbl flags? Watch it scan a demo app, no repo or account needed.
See a live scan →Secrets you need to use later: encrypt them
When you store something you must read back, an API token you use to call a service on the user's behalf, a connection string, you encrypt it, because you need the real value again. Use an authenticated algorithm and keep the key in your secrets manager, not the database:
import crypto from "node:crypto";
const key = Buffer.from(process.env.ENCRYPTION_KEY, "hex"); // from secrets, not code
function encrypt(text) {
const iv = crypto.randomBytes(12);
const c = crypto.createCipheriv("aes-256-gcm", key, iv);
const enc = Buffer.concat([c.update(text, "utf8"), c.final()]);
return Buffer.concat([iv, c.getAuthTag(), enc]).toString("hex");
}A hardcoded key defeats the whole point; the fix for keeping keys out of the client and out of source applies here too.
Ordinary user data: usually neither
Most fields, a name, an email, a preference, do not need application-level encryption. They are protected by controlling who can reach the database and by not leaking them through open endpoints or exposed keys. Encrypting every column adds key-management burden for little gain if the real risk is an open database anyone can read. Spend the effort there first: lock down access, then reserve field-level encryption for data that genuinely warrants it.
The rule in one line
If you need to verify it, hash it. If you need to read it back, encrypt it with a key kept out of the database. If it is ordinary data, protect it with access control, not a crypto layer you do not need.
Frequently asked questions
What is the difference between hashing and encryption?
Encryption is reversible: with the key, you can turn the ciphertext back into the original value. Hashing is one-way: you cannot recover the input from the hash, only check whether a given input produces the same hash. So you encrypt data you need to read back later, and you hash data you only ever need to verify, like a password, where you never need the original again.
Why is encrypting a password wrong?
Because encryption is reversible, which means anyone with the key can recover every password, so a key leak becomes a password leak. You never need to read a password back; you only need to check whether a login attempt matches. That is exactly what hashing is for. Encrypting passwords adds a single point of failure, the key, that hashing does not have. Passwords get hashed, always, with a slow algorithm like bcrypt.
Does ordinary data like a name or email need encryption?
Usually not at the application level. Most personal data is protected by controlling who can access the database and making sure your app does not leak it through open endpoints or exposed keys. Encrypting every field adds complexity and key-management burden for little gain if the real risk is an open database. Reserve field-level encryption for genuinely sensitive data with a specific compliance or exposure reason.
What about API tokens and secrets I store for users?
Those you encrypt, because you need to use the real value later to call the service on the user's behalf. Store them encrypted with a key kept in your secrets manager, not in the database or the code. That way a database leak alone does not expose the tokens. This is the case where reversibility is a requirement, so encryption, not hashing, is the right tool.
Check how your app stores sensitive data
A scan flags the storage mistakes that matter most: a weak password hash, a hardcoded encryption key, and secrets exposed where they should not be. Run a free scan and see what your app is doing with its data.