Blog | G5 Cyber Security

bcrypt: False Positives – Salt/Hash Mismatch

TL;DR

If bcrypt.checkpw() is incorrectly validating passwords (returning true for different salt+hash combinations), it almost always means you’re not storing the full hash correctly, or are accidentally modifying it during retrieval/comparison. This guide explains how to diagnose and fix this.

1. Understand How bcrypt Works

bcrypt doesn’t store passwords directly. It stores a hash of the password along with a randomly generated salt. The salt is unique for each password. When you try to log in, bcrypt hashes the entered password using the stored salt and compares that result to the stored hash.

2. Common Causes

The most frequent problems are related to how the salt and hash are handled:

3. Diagnosing the Problem

  1. Retrieve and Print the Stored Hash: The first step is to verify what’s actually stored in your database. Use a debugging tool or log statement to print the full value of the hash associated with a test user.
    # Example (Python) - replace with your actual database code
    import sqlite3
    conn = sqlite3.connect('your_database.db')
    hash_from_db = conn.execute("SELECT password_hash FROM users WHERE username = 'testuser'" ).fetchone()[0]
    print(f"Stored hash: {hash_from_db}")
    conn.close()
    
  2. Hash the Test Password Manually: Hash the test password using the same bcrypt library and settings you use for registration.
    # Example (Python)
    import bcrypt
    password = "testpassword"
    salt = bcrypt.gensalt()
    hashed_password = bcrypt.hashpw(password.encode('utf-8'), salt)
    print(f"Generated hash: {hashed_password.decode('utf-8')}")
    
  3. Compare the Strings Directly: Carefully compare the stored hash from step 1 with the generated hash from step 2. They should be identical.

    If they are different, proceed to the next steps.

4. Fixing Incorrect Storage

  1. Store the Full Hash: Ensure you’re storing the complete string returned by bcrypt.hashpw(). This includes the salt and hash combined.
    # Example (Python)
    hashed_password = bcrypt.hashpw(password.encode('utf-8'), salt)
    conn.execute("UPDATE users SET password_hash = ? WHERE username = ?", (hashed_password.decode('utf-8'), 'testuser'))
    
  2. Database Column Size: Verify that your database column for storing passwords is large enough to accommodate the full bcrypt hash (at least 60 characters). Increase the column size if necessary.

    For example, in MySQL you might use:

    ALTER TABLE users MODIFY password_hash VARCHAR(255);

5. Addressing Character Encoding Issues

  1. Consistent Encoding: Always encode passwords to UTF-8 before hashing and decode the stored hash back to UTF-8 when comparing.

    See the examples in steps 2 & 3.

  2. Database Collation: Ensure your database column uses a UTF-8 collation (e.g., utf8mb4_unicode_ci for MySQL). This helps prevent character corruption.

6. Prevent Accidental Modification

  1. Direct Retrieval: Retrieve the hash directly from the database without any unnecessary transformations or string manipulation.
  2. Whitespace Handling: Be careful not to strip leading/trailing whitespace from the stored hash during retrieval.

7. Testing

After making changes, re-run steps 1 and 2 to confirm that the generated hash matches the stored hash exactly. Test with multiple users and passwords.

Exit mobile version