Skip to content

bcrypt.compare accepts NUL-byte suffixed hashes (non-canonical input) #1224

Description

@fasrm

bcrypt.compare() accepts a non-canonical hash string when a valid bcrypt hash is followed by a NUL byte (\x00) and arbitrary suffix data.

This happens because the native binding passes the hash as a NUL-terminated C string (encrypted.c_str()), so the underlying bcrypt implementation ignores everything after the first \0.

As a result:

  • JavaScript treats the string as distinct
  • Native bcrypt truncates at \0 and validates the original hash

Impact
Non-canonical hashes can validate successfully
Application logic relying on raw hash identity may be bypassed
Cross-language inconsistency:

  • Node bcrypt: accepts
  • Python bcrypt: rejects

This may affect systems using multiple services (Node + Python) or performing hash validation/migration outside Node.

Proof of Concept (Node)
const bcrypt = require("bcrypt");

(async () => {
const password = "test-password";
const hash = await bcrypt.hash(password, 10);

const nulVariant = hash + "\x00ATTACKER_SUFFIX";
const dotVariant = hash + ".ATTACKER_SUFFIX";

console.log("compare(hash):", await bcrypt.compare(password, hash));
console.log("compare(nulVariant):", await bcrypt.compare(password, nulVariant));
console.log("compare(dotVariant):", await bcrypt.compare(password, dotVariant));
})();

Output:
compare(hash): true
compare(nulVariant): true
compare(dotVariant): false

Cross-language behavior (Python)
import bcrypt

password = b"test-password"

hash = b"$2b$10$..." # same hash from Node
nulVariant = hash + b"\x00ATTACKER_SUFFIX"

print(bcrypt.checkpw(password, hash)) # True
print(bcrypt.checkpw(password, nulVariant)) # False

Root Cause
In bcrypt_node.cc:
bcrypt(input.c_str(), input.length(), encrypted.c_str(), bcrypted);

Password is length-aware
Hash is passed as NUL-terminated C string
This causes truncation at the first \0.

Expected Behavior
bcrypt.compare() should reject non-canonical hash inputs (e.g., containing \x00 or extra trailing data).

Suggested Fix
Validate hash format before calling native bcrypt
Reject inputs containing \x00
Enforce canonical bcrypt length (60 chars)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions