Home/Blog/bcrypt Hash Verification Error: Invalid Salt / Password Fix
šŸ”
bcryptpassword hashingnodejs

bcrypt Hash Verification Error: Invalid Salt / Password Fix

bcrypt errors like 'Invalid salt version', 'data and hash arguments required', and 'Invalid hash provided' happen for specific reasons. Learn exactly why and how to fix them.

May 22, 20264 min readby ToolNinja

The Exact Error

Error: Invalid salt version
Error: data and hash arguments required
Error: Invalid hash provided to bcrypt
Error: data must not be empty

Or the silent failure:

const match = await bcrypt.compare(password, hash);
// match === false  (even though the password is correct)

Quick summary: bcrypt errors come from passing the wrong arguments — an empty string, a non-bcrypt hash, or comparing an already-hashed password against a stored hash.


Why This Error Happens

1. Hash stored incorrectly — truncated column, encoding mismatch, or the stored value is not a bcrypt hash

2. Double hashing — password hashed before being passed to bcrypt.compare(), comparing hash-vs-hash

3. Empty input — bcrypt.hash(undefined) or bcrypt.hash('') depending on version

4. Wrong library — mixing bcryptjs (pure JS) and bcrypt (native) hashes

5. Hash truncation — database column too short (e.g., VARCHAR(50) instead of VARCHAR(60))


Step-by-Step Diagnosis

Step 1 — Inspect the stored hash

console.log(hash);
// Valid bcrypt hash looks like:
// $2b$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
// Must be exactly 60 characters, start with $2a$, $2b$, or $2y$
console.log(hash.length); // Should be 60
console.log(hash.startsWith('$2'));

Step 2 — Check the registration code for double hashing

// WRONG — hashing the password before storage AND before compare
app.post('/login', async (req, res) => {
  const inputHash = await bcrypt.hash(req.body.password, 10); // Don't do this!
  const match = await bcrypt.compare(inputHash, user.hash);
});

// RIGHT
app.post('/login', async (req, res) => {
  const match = await bcrypt.compare(req.body.password, user.hash);
});

Step 3 — Check for empty values

if (!password) throw new Error('Password is required');
if (!hash) throw new Error('Hash is required');
const match = await bcrypt.compare(password, hash);

Solutions

Solution 1 — Fix database column length

-- bcrypt hashes are always 60 characters
ALTER TABLE users MODIFY COLUMN password_hash VARCHAR(60) NOT NULL;

Solution 2 — Only hash on write, compare plain on read

// Registration (write path) — hash once
const hash = await bcrypt.hash(plainPassword, 10);
await db.query('INSERT INTO users (password_hash) VALUES (?)', [hash]);

// Login (read path) — compare plain password
const storedHash = user.password_hash;
const match = await bcrypt.compare(plainPassword, storedHash);

Solution 3 — Validate before hashing

async function hashPassword(plain) {
  if (!plain || typeof plain !== 'string') {
    throw new Error('Password must be a non-empty string');
  }
  return bcrypt.hash(plain, 10);
}

async function verifyPassword(plain, hash) {
  if (!plain || !hash) return false;
  if (!hash.startsWith('$2')) return false;
  return bcrypt.compare(plain, hash);
}

Real-World Examples

Express + Mongoose registration:

userSchema.pre('save', async function(next) {
  if (!this.isModified('password')) return next();
  this.password = await bcrypt.hash(this.password, 12);
  next();
});

// On login — do NOT hash again
userSchema.methods.comparePassword = async function(candidate) {
  return bcrypt.compare(candidate, this.password);
};

Quick Reference — bcrypt Error Causes

ErrorMost Likely Cause
Invalid salt versionHash is not bcrypt (wrong prefix)
data and hash requiredPassing undefined/null
match === false (unexpectedly)Double-hashing the password
Invalid hash providedHash is truncated or corrupted
Hash too longInput exceeds 72 bytes (bcrypt limit)

Prevent This Error in the Future

1. Use VARCHAR(60) for hash columns — bcrypt output is always exactly 60 characters.

2. Never pre-hash before compare() — only the plain password goes into compare().

3. Log the hash on registration during development to confirm it looks correct.


Use ToolNinja to Debug Faster

The Hash Generator lets you create bcrypt hashes and verify them interactively — useful for testing the correct round count and verifying that a known password matches a stored hash.

šŸ”§ Hash Generator — toolninja.io/tools/hash-generator

Share:š• Twitterin LinkedIn

Frequently Asked Questions

What does 'Invalid salt version' mean in bcrypt?

The stored hash does not start with a recognized bcrypt prefix ($2a$, $2b$, or $2y$). This usually means the hash was truncated, stored incorrectly in the database, or is not a bcrypt hash at all.

Why does bcrypt.compare() return false even when the password is correct?

The most common cause is that the hash was hashed twice — once when saving and once before comparing. Only hash passwords on the write path; on the read path, pass the plain password to compare().

Can I use bcrypt to hash things other than passwords?

Technically yes, but bcrypt truncates input at 72 bytes. Long strings beyond 72 bytes produce the same hash, which can be a security issue. For arbitrary data hashing, use SHA-256 or similar.

🄷 ToolNinja