====== PHP RFC: Deprecate and remove support for passwords longer than 72 bytes in password_hash() with bcrypt ====== * Version: 0.1 * Date: 2026-09-27 * Author: Sjoerd Langkemper * Status: Draft * First Opened: 2026-09-27 * Implementation: tbd * Discussion thread: tbd * Voting thread: tbd ===== 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 (derived from Openwall's ''crypt_blowfish'' implementation). 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 during key derivation and cost calculations. Historically, PHP has silently truncated inputs longer than 72 bytes. When applications misuse ''password_hash()''—for example, by prepending static salts, pepper nonces, or long identifiers to passwords before hashing—this silent truncation 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''. ==== 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 catastrophic backward-compatibility break. Existing users who registered under legacy 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. ==== Why ValueError applies instead of generic Exception ==== In PHP's exception hierarchy, a ''ValueError'' indicates a **programming error** (passing an argument of correct type but invalid value/range to a function) 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 (e.g., a user submitting a very long password). However, throwing a ''ValueError'' is appropriate here because exceeding 72 bytes in ''password_hash()'' is fundamentally a programming error for two reasons: 1. **Misuse of function parameters**: 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()''. 2. **Failure to account for algorithm limits**: Calling ''password_hash()'' with raw input exceeding algorithm bounds without prior validation or pre-hashing (such as raw SHA-256/SHA-512 outputs) 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. ===== 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. ===== 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). * **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. Deprecate passing passwords > 72 bytes to password_hash() with PASSWORD_BCRYPT in PHP 8.7 and throw a ValueError in PHP 8.8? * Yes * No ===== Patches and Tests ===== * Reference Implementation: [[https://github.com/php/php-src/pull/23076|php/php-src Pull Request #23076]] ===== References ===== * **GitHub PR**: [[https://github.com/php/php-src/pull/23076|php/php-src #23076: Bcrypt 72 byte password handling]] * **PHP Internals Discussion**: [[https://news-web.php.net/php.internals/75692|Internals Discussion on 72-char restriction]] * **Previous PHP RFC**: [[https://wiki.php.net/rfc/password_hash_spec|PHP RFC: password_hash() function behavior (2014)]] * **PHP Bug Tracker**: [[https://bugs.php.net/bug.php?id=67653|Bug #67653: PASSWORD_BCRYPT truncates password longer than 72 bytes silently]] * **FreshRSS CVE Analysis**: [[https://pentesterlab.com/blog/freshrss-bcrypt-truncation-auth-bypass|PentesterLab: FreshRSS Bcrypt Truncation Auth Bypass (CVE-2025-68402)]] * **Invicti Security Writeup**: [[https://www.invicti.com/blog/web-security/okta-vulnerability-bcrypt-auth|Okta Vulnerability & Bcrypt Truncation]] * **PHP Wiki RFC Template**: [[https://wiki.php.net/rfc/template|PHP Wiki RFC Template]] ===== Changelog ===== * 2026-09-27: Revised introduction structure, added rationale for ValueError, updated Future Scope for password_verify, and relocated previous internals discussions to references.