The Pre Hash Nobody Keyed

A team knows bcrypt has a 72-byte input limit and writes this to get around it.

import bcrypt, hashlib

def store(password: str) -> bytes:
    # "bcrypt truncates long passwords, so hash first"
    digest = hashlib.sha256(password.encode()).digest()   # 32 raw bytes
    return bcrypt.hashpw(digest, bcrypt.gensalt(12))

The binding accepts the raw digest without complaint and every test passes. What is the defect?

A

Pre-hashing with SHA-256 makes the construction as fast to attack as plain SHA-256, because the attacker can precompute digests and skip the bcrypt step.

B

The pre-hash is unkeyed, so the construction is open to password shucking: an attacker holding an unsalted SHA-256 digest of the same password from an unrelated breach can test that digest against this bcrypt hash directly, without guessing the password once. OWASP's recipe keys the inner hash with a pepper and encodes it: bcrypt(base64(hmac-sha384(data:$password, key:$pepper)), $salt, $cost).

C

Nothing. Pre-hashing removes the 72-byte ceiling and a cost factor of 12 is above the recommended minimum, so this is the documented pattern.

D

The cost factor is too low. OWASP's minimum work factor for bcrypt is 14, so 12 leaves the hashes cheap to attack offline.