Take the State of PHP 2026 Survey →

Throw error for passwords longer than 72 bytes in password_hash() with bcrypt View as Markdown

Inactive
Authors
Sjoerd Langkemper sjoerd-php@linuxonly.nl
Date
2026-09-27
Version
0.1
Implementation
tbd
Source
wiki.php.net/rfc/bcrypt_max_password_length

Vote

Throw error on bcrypt passwords longer than 72 bytes as described in the RFC? no votes
Yes · No · Abstain

Discussions on the mailing list

crypt() BC issue
by Yasuo Ohgaki · 32 messages
[RFC] Add PASSWORD_BCRYPT_SHA256 to password_hash
by Sjoerd Langkemper · 12 messages

RFC text

Copied from wiki.php.net, last synchronized Oct 13, 2026 11:53 UTC

Introduction

This RFC proposes to enforce input length limits in password_hash() for PASSWORD_BCRYPT by emitting a deprecation notice in PHP 8.7 and throwing a ValueError in PHP 8.8.

The PASSWORD_BCRYPT algorithm in PHP's password_hash() wraps standard bcrypt. Bcrypt operates on a fixed key schedule with a hard maximum password length of 72 bytes. Any input characters beyond byte 72 are silently discarded.

Historically, PHP has silently truncated inputs longer than 72 bytes. Passing longer strings to password_hash can result in severe authentication bypass vulnerabilities:

  • CVE-2025-68402 (FreshRSS): Prepending a 64-character nonce before a 60-character bcrypt hash caused the 72-byte truncation window to contain only the static nonce and algorithm headers ($2y$10$), discarding all password-dependent data and allowing authentication bypass with any password.
  • Okta Authentication Bypass: Exceeding character length boundaries in bcrypt cache key generation led to hash comparison failures and authentication bypasses under specific conditions.

Proposal

When password_hash() is called with PASSWORD_BCRYPT (or PASSWORD_DEFAULT when resolving to bcrypt) and the input $password string exceeds 72 bytes in length:

  • In PHP 8.7: Emit an E_DEPRECATED notice.
  • In PHP 8.8: Throw a ValueError.

Users don't use passwords longer than 72 bytes

The rockyou password dump contains 772 out of 14344391 passwords that are longer than 72 characters. However, many of those are obvious form spam, keyboard mashing, or just copy/pasted content, not actual passwords. I couldn't find an obvious actual password in the set. This is not conclusive, since passwords can be any string and don't have to look like passwords.

Adobe's 2013 breach contains 152989508 passwords encrypted with an 8-byte block cipher. The longest password is 11 blocks (88 bytes), but it's the same block 10 times, indicating a repetitive password. There's one password that takes up 9 blocks (72 bytes), and several that take up 7 blocks (56 bytes).

So depending on how you look at it, passwords longer than 72 characters occur between never and 0.005% of the time.

Why ValueError applies instead of generic Exception

In PHP's exception hierarchy, a ValueError indicates a programming error rather than a runtime operational/input error that an application should catch and recover from dynamically.

One might argue that receiving a long string is a dynamic runtime input error. However, throwing a ValueError is appropriate here because exceeding 72 bytes in password_hash() is fundamentally a programming error for two reasons:

  • Standard end-user passwords practically never exceed 72 bytes. When string inputs to password_hash() exceed this threshold, it almost exclusively indicates that developers are improperly concatenating static pepper secrets, nonces, or secondary hashes into the password field before passing it into password_hash().
  • Calling password_hash() with raw input exceeding algorithm bounds without prior validation or pre-hashing represents a developer oversight in design rather than an unexpected end-user input state.

Throwing a ValueError ensures developers catch this misconfiguration during development and testing rather than allowing insecure hashing patterns into production.

Why password_verify() is left unchanged

This RFC explicitly does not modify password_verify().

Applying a failure or exception to password_verify() for inputs exceeding 72 bytes would introduce a severe backward-compatibility break. Existing users who registered under previous versions with passphrases longer than 72 bytes (or whose applications prepended long secrets) would be permanently unable to authenticate. Leaving password_verify() untouched preserves access for existing credentials while preventing the creation of new truncated hashes.

Examples

// Valid passwords (<= 72 bytes) work normally across all versions:
$hash = password_hash('correct-horse-battery-staple', PASSWORD_BCRYPT);
 
// Passwords > 72 bytes:
 
// PHP 8.7 Behavior:
// E_DEPRECATED: password_hash(): Passing a password longer than 72 bytes to the bcrypt algorithm is deprecated
$longPassword = str_repeat('a', 80);
$hash = password_hash($longPassword, PASSWORD_BCRYPT);
 
// PHP 8.8+ Behavior:
try {
    $hash = password_hash($longPassword, PASSWORD_BCRYPT);
} catch (ValueError $e) {
    // Throws: ValueError: password_hash(): Password length cannot exceed 72 bytes when using PASSWORD_BCRYPT
}

Backward Incompatible Changes

  • PHP 8.7: Applications passing strings longer than 72 bytes to password_hash() with PASSWORD_BCRYPT will trigger an E_DEPRECATED notice.
  • PHP 8.8: Passing strings longer than 72 bytes to password_hash() with PASSWORD_BCRYPT will throw a ValueError, failing unhandled calls.

Applications allowing long passphrases or prepending pepper/nonces should validate password length, pre-hash input (e.g. using raw SHA-256/SHA-512 outputs before passing to bcrypt), or migrate to algorithms without 72-byte restrictions, such as Argon2id (PASSWORD_ARGON2ID).

Proposed PHP Version(s)

  • PHP 8.7: Deprecation notice emitted.
  • PHP 8.8: Upgraded to ValueError.

RFC Impact

To the Ecosystem

Frameworks and authentication libraries that permit arbitrary-length input strings or prepend pepper secret strings will need to enforce length validation or pre-hashing before calling password_hash().

To Existing Extensions

None.

To SAPIs

None.

Discussion

Opposition arguments:

  • Backward compatibility is a major concern: users with existing long passwords could suddenly face exceptions, breaking previously working logins. Introducing a ValueError risks crashing applications for users who happen to submit long passwords.
  • The limit is 72 *bytes*, not characters, so non-ASCII users (especially those using multi-byte scripts) would be disproportionately affected.
  • 72 bytes already provides enough entropy for strong passphrases (e.g., 8-word Diceware), so rejecting longer input is seen as a usability cost without a real security gain.
  • Truncation is simply documented, correct algorithm behavior, not “corruption” of input.
  • Rehashing during login (via password_needs_rehash()) could still surface the error for existing users with long passwords when algorithm parameters change.

Support arguments:

  • Silent truncation already causes real harm, such as false-positive logins and broken “password must differ from previous” checks; a noisy failure is preferable because it prompts developers to fix the underlying issue.
  • NIST guidance explicitly advises against truncating passwords, and real-world incidents (including at major companies) show truncation behavior has contributed to security issues.
  • An API that silently accepts and mishandles long input is a poor design; developers deserve a clear signal rather than a hidden security gap.

Open Issues

None at present.

Future Scope

  • Handling length limits in password_verify(): Future work may explore how to safely address inputs longer than 72 bytes in password_verify() without introducing authentication lockouts for existing legacy hashes (e.g., via opt-in flags or distinct verification modes).
  • Handling length limits in crypt()
  • Algorithm Migration: Encouraging applications to migrate toward modern memory-hard hashing standards like Argon2id (PASSWORD_ARGON2ID), which do not suffer from the 72-byte key truncation limit.

Voting Choices

Primary Vote: Requires 2/3 majority.

Vote: Throw error on bcrypt passwords longer than 72 bytes as described in the RFC? — Yes: 0 · No: 0 · Abstain: 0 (closed)

Patches and Tests

References

Changelog

  • 2026-09-27: Revised introduction structure, added rationale for ValueError, updated Future Scope for password_verify, and relocated previous internals discussions to references.