In PHP, what is bcrypt's 72-byte limit in password_hash(), and how does it bite long or multibyte passwords?
answer
- bcrypt truncates input at 72 bytes
- bytes, not characters — multibyte counts double
- silent truncation, no error
- two long passwords can collide
- ARGON2ID has no such limit
basics
~20 sWith PASSWORD_BCRYPT, password_hash() silently truncates the password to 72 bytes, so anything past byte 72 is ignored. Because it counts bytes, multibyte UTF-8 characters use several each. Two long passwords sharing a 72-byte prefix verify as equal. ARGON2ID has no such limit.
solid answer
~50 sbcrypt operates on at most **72 bytes** of input, and `password_hash($pw, PASSWORD_BCRYPT)` **silently truncates** anything longer — there is no warning or error. The limit is in **bytes, not characters**, so a passphrase of multibyte UTF-8 characters (each emoji or accented letter is 2–4 bytes) can hit 72 bytes well before 72 characters. Two consequences: a very long password is only as strong as its first 72 bytes, and **two distinct passwords that share the same first 72 bytes will `password_verify` as identical**. This is a property of bcrypt itself, not a PHP bug, but it surprises people who set a high maximum length. Mitigations: use `PASSWORD_ARGON2ID`, which has **no 72-byte limit**; or, if you must stay on bcrypt, pre-hash into a fixed-size printable value first (for example Base64-encode an HMAC-SHA-256 of the password) so the bcrypt input is short and null-byte-free. Do not simply reject passwords over 72 characters — that both annoys users and misdescribes the limit, which is in bytes.
go deeper
Know that bcrypt only uses the first 72 bytes and that Argon2id does not have this limit.
Explain that truncation is silent and counted in bytes, so multibyte passwords hit it sooner, and that shared 72-byte prefixes collide.
Diagnose a 'wrong password still works' report as truncation and fix it with Argon2id or a pre-hash to a short value.
Decide the org policy on maximum length and pre-hashing, balancing user experience against the bcrypt constraint.
## The limit, precisely Bcrypt's design processes only the first **72 bytes** of the password. In PHP, `password_hash($password, PASSWORD_BCRYPT)` (and therefore `PASSWORD_DEFAULT` while it resolves to bcrypt) reflects that: the manual states the password "being truncated to a maximum length of 72 bytes." The truncation is **silent** — you get a normal-looking hash back, and `password_verify` will later accept any password whose first 72 bytes match. ## Why "bytes, not characters" matters PHP strings are byte strings. UTF-8 encodes: - ASCII (`a`, `1`, `!`) as **1 byte**, - most accented Latin and Greek/Cyrillic letters as **2 bytes**, - most CJK characters as **3 bytes**, - emoji and many symbols as **4 bytes**. So a "passphrase" like a string of emoji reaches the 72-byte ceiling in as few as ~18 characters. A user who thinks they typed a 40-character password may have supplied only its first 30 or so characters to bcrypt. ## The two failure modes | Symptom | Cause | |---|---| | A long password "works" even when the user mistypes the tail | Everything past byte 72 was truncated before hashing | | Two different long passwords log into the same account | They share the same first 72 bytes | Neither is exploitable in most systems (an attacker still needs the first 72 bytes), but both violate the intuition that the whole password is checked, and the second is a real correctness bug for users with long secrets or password-manager output. ## What to do about it 1. **Prefer `PASSWORD_ARGON2ID`.** Argon2 has no 72-byte input limit and is memory-hard. If your PHP build includes Argon2 support, this removes the problem outright. 2. **If bcrypt is required, pre-hash to a fixed short value.** A common pattern is to compute a keyed digest and Base64-encode it so the bcrypt input is short and contains no NUL byte: ```php $pre = base64_encode(hash_hmac('sha256', $password, $pepperKey, true)); $hash = password_hash($pre, PASSWORD_BCRYPT); ``` Verification pre-hashes the same way before `password_verify`. (This also folds in a server-side pepper; the pepper *design* is a concept-home topic — here the point is only that the bcrypt input becomes short and safe.) 3. **Do not cap length at 72 characters.** The limit is in bytes, so a byte-aware check would be `strlen($pw) <= 72`, but rejecting long passwords is user-hostile and unnecessary if you use Argon2id or pre-hashing. Allow long passwords and handle the limit correctly instead. ## A note on NUL bytes Historically, a NUL byte (``) inside a password could truncate bcrypt input early; pre-hashing to Base64 avoids embedding NULs. This is another reason the pre-hash pattern uses a printable encoding rather than raw binary from `hash_hmac(..., true)`. The choice between bcrypt and Argon2 as *algorithms* is a security-design question; the PHP-specific facts here are the silent 72-byte (not 72-character) truncation, that Argon2id avoids it, and the pre-hash workaround.
- Would capping the input at 72 *characters* fix the problem?Not correctly. The limit is 72 *bytes*, so 72 multibyte characters can be far more than 72 bytes, and you would still truncate. A byte check (`strlen($pw) <= 72`) is accurate, but rejecting long passwords is unnecessary — use Argon2id or pre-hash instead so you can accept passwords of any length.
- Does the 72-byte limit apply to `PASSWORD_ARGON2ID`?No. The 72-byte ceiling is specific to bcrypt's construction. `PASSWORD_ARGON2ID` (and `PASSWORD_ARGON2I`) process the full input, so switching to Argon2id removes the truncation and its collision surprise entirely, provided your PHP build has Argon2 support.
- Why Base64-encode the pre-hash instead of using raw HMAC bytes?Raw binary can contain a NUL byte, which historically truncates bcrypt input early, and it isn't a clean string to pass around. Base64 (or hex) yields a short, printable, NUL-free value that safely fits under 72 bytes, so the bcrypt input is well-defined.
saying these in an interview costs you the question
- bcrypt truncates at 72 characters, so multibyte is fine
- password_hash raises an error when the password is too long
- the 72-byte limit also applies to Argon2id
- a 100-character password is fully checked by bcrypt
- you must reject any password longer than 72 characters