# RFC: Throw error for passwords longer than 72 bytes in password_hash() with bcrypt

- Status: Inactive
- Authors: Sjoerd Langkemper sjoerd-php@linuxonly.nl
- Date: 2026-09-27
- Version: 0.1
- Implementation: tbd
- Original: https://wiki.php.net/rfc/bcrypt_max_password_length
- Page: https://externals.io/rfc/bcrypt_max_password_length
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

## Votes

### Throw error on bcrypt passwords longer than 72 bytes as described in the RFC? (closed)

Yes: 0 · No: 0 · Abstain: 0

## Discussions on the mailing list

- [crypt() BC issue](https://externals.io/message/75570.md) — started on 2014-07-16, 32 messages
- [\[RFC\] Throw error for passwords longer than 72 bytes in password_hash() with bcrypt](https://externals.io/message/132689.md) — started on 2026-09-29, 37 messages
- [\[RFC\] Add PASSWORD_BCRYPT_SHA256 to password_hash](https://externals.io/message/132784.md) — started on 2026-10-05, 12 messages

---

## 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

```php
// 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

- Reference Implementation: [php/php-src Pull Request #23076](https://github.com/php/php-src/pull/23076)

## References

- GitHub PR: [php/php-src #23076: Bcrypt 72 byte password handling](https://github.com/php/php-src/pull/23076)
- PHP Internals Discussion: [Internals Discussion on 72-char restriction](https://news-web.php.net/php.internals/75692)
- Previous PHP RFC: [PHP RFC: password\_hash() function behavior (2014)](https://externals.io/rfc/password_hash_spec)
- PHP Bug Tracker: [Bug #67653: PASSWORD\_BCRYPT truncates password longer than 72 bytes silently](https://bugs.php.net/bug.php?id=67653)
- FreshRSS CVE Analysis: [PentesterLab: FreshRSS Bcrypt Truncation Auth Bypass (CVE-2025-68402)](https://pentesterlab.com/blog/freshrss-bcrypt-truncation-auth-bypass)
- Invicti Security Writeup: [Okta Vulnerability &amp; Bcrypt Truncation](https://www.invicti.com/blog/web-security/okta-vulnerability-bcrypt-auth)
- PHP Wiki RFC Template: [PHP Wiki RFC Template](https://wiki.php.net/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.
