Caption: Three columns I will not mix up. Today's password_hash() is silent. bcrypt_max_password_length is a proposed 8.7/8.8 break that did not vote. PASSWORD_BCRYPT_SHA256 is a later, still-draft opt-in. PASSWORD_DEFAULT is still bcrypt.
I did not get E_DEPRECATED. I ran Ubuntu 24.04 archive PHP 8.3.6 (php -v built 2 Sep 2026) and hashed an 80-byte string with PASSWORD_BCRYPT. password_verify returned true for the full 80 bytes and for the first 72. PASSWORD_DEFAULT printed 2y. PASSWORD_BCRYPT_SHA256 is not defined.
php.net’s password_hash manual still cautions that PASSWORD_BCRYPT truncates to 72 bytes. php.watch still lists PASSWORD_DEFAULT as "2y" through 8.5 and 8.6. This is a lab note. I did not reproduce FreshRSS. The failure I hit is the quiet one: two different strings, one hash.
What changed
Two RFCs in ten days. Zero yes votes. PHP 8.6 still has no GA.
What I will treat as locked this week:
- Silent truncation is still documented. php.net’s caution is current as of 9 Oct 2026. This Ubuntu 24.04 CLI hashed
$2y$10$— PHP 8.3 still defaults bcrypt cost 10. The bump to 12 is a PHP 8.4 change. Laravel’sBCRYPT_ROUNDSdefault is already 12. Distropassword_hash()is not Laravel’s hasher. - RFC: throw on bcrypt passwords longer than 72 bytes. wiki.php.net/rfc/bcrypt_max_password_length, changelog 2026-09-27. Sjoerd Langkemper announced it 29 Sep 2026 11:33:05 UTC (internals 132689). Proposed:
E_DEPRECATEDin 8.7,ValueErrorin 8.8.password_verify()stays unchanged. Vote table 0–0–0. PR: php-src#23076. - This Week in PHP Internals | Oct 1, 2026 (Discourse copy 2 Oct 2026) made it the week’s top story. Recap, not a merge.
- Follow-up RFC:
PASSWORD_BCRYPT_SHA256. wiki.php.net/rfc/bcrypt_sha256, changelog 2026-10-04. Internals 5 Oct 2026 09:20:29 UTC (132784). Proposed next PHP 8.x (e.g. 8.7).PASSWORD_DEFAULTunchanged. PR php-src#24073 opened 2 Oct, last cite 6 Oct, still open. Sjoerd wrote the throw RFC “receives much criticism because of its backwards incompatibility, and I don’t think it is worth pursuing further at the moment.” - Laravel still defaults to bcrypt; the 72-byte brake is off. Laravel 13 hashing docs: driver bcrypt. 13.x
config/hashing.php:'limit' => env('BCRYPT_LIMIT', null). BcryptHasher throwsInvalidArgumentExceptiononly when$this->limitis set. laravel/framework#54509 merged 10 Feb 2025. Opt-in, not a Laravel-core CVE. - Cited failure: FreshRSS CVE-2025-68402, GHSA-pcq9-mq6m-mvmp published 8 Mar 2026: a 64-character nonce prepended so bcrypt never saw the password. Edge branch only, never a stable release. I am citing it, not reproducing it.
What I will treat as rumor if it lands in Slack:
- The throw RFC shipped in 8.5 or 8.6. It did not. php.watch 8.6 releases (retrieved 9 Oct 2026): 8.6.0RC3 on 8 Oct 2026. GA still 19 Nov 2026. Hard freeze was 22 Sep 2026.
PASSWORD_BCRYPT_SHA256is in FPM. Missing on this 8.3 CLI. That vote table is also 0–0–0.- A Laravel-core CVE, or
password_verifystarting to throw. Illuminate has no GHSA for this. The first RFC leaves verify unchanged so existing long passphrases still log in.
What I would change in a real Laravel/PHP app this week
Three edits. None of them is “wait for 8.8.”
1. Run the CLI lab on the binary Nginx actually uses. Do not paste a fake PASSWORD_BCRYPT_SHA256.
php -v
php -r 'echo PASSWORD_DEFAULT, PHP_EOL, defined("PASSWORD_BCRYPT_SHA256") ? "yes" : "no", PHP_EOL, implode(",", password_algos()), PHP_EOL;'
php-fpm8.3 -v # or php-fpm8.4 / php-fpm8.5 — the pool in the vhost
Then the hash check the RFC’s own examples describe. This is the before. I am not inventing the 8.7 notice:
$long = str_repeat('a', 80);
$hash = password_hash($long, PASSWORD_BCRYPT);
var_dump(password_verify($long, $hash)); // true
var_dump(password_verify(substr($long, 0, 72), $hash)); // true
var_dump(password_verify(str_repeat('a', 72) . 'bbbbbbbb', $hash)); // true
On this box that printed:
PHP 8.3.6 (cli) (built: Sep 2 2026)
PASSWORD_DEFAULT=2y
defined_BCRYPT_SHA256=no
verify_80=true
verify_first_72=true
verify_72a_plus_b=true
No deprecation handler fired. CLI 8.3.6 and an 8.5 FPM socket are two labs.
2. Stop concatenating a pepper into bcrypt. Hash the password. Limit bytes, not characters.
The wall in app/ is Hash::make($pepper . $request->password) filling seventy-two bytes with a static prefix. Laravel 13 will not save me while BCRYPT_LIMIT is null.
use Illuminate\Support\Facades\Hash;
use InvalidArgumentException;
$password = $request->string('password')->toString();
// Before — bcrypt sees strlen() bytes, not graphemes.
// A 40-byte pepper + a normal password already crosses 72.
// Hash::make($pepper . $password);
// After — keep bcrypt, but refuse over-long input in bytes.
// hashing.bcrypt.limit is still null unless I set BCRYPT_LIMIT=72.
if (strlen($password) > 72) {
throw new InvalidArgumentException('Password exceeds bcrypt 72-byte window.');
}
$hash = Hash::make($password);
Password::max(72) counts characters. bcrypt cuts bytes. A 36-character Cyrillic passphrase is already 72 bytes. If I need a pepper, HMAC then argon2id — not $pepper . $password into bcrypt. Laravel did not grow PASSWORD_BCRYPT_SHA256 this week.
3. Turn the brake on, or leave bcrypt.
HASH_DRIVER=bcrypt
BCRYPT_ROUNDS=12
BCRYPT_LIMIT=72
Or, if password_algos() already lists argon2id (it did on this 8.3.6 CLI):
HASH_DRIVER=argon2id
ARGON_MEMORY=65536
ARGON_TIME=4
ARGON_THREADS=1
Then php artisan config:publish hashing if the file is missing, and php artisan config:clear. Cached config is how I would edit .env and still hash bcrypt for a week. Hash::check still verifies old $2y$ rows after a driver change. rehash_on_login is true in 13.x. If BCRYPT_LIMIT=72 is on and rounds also changed, a long passphrase can 500 on login rehash. That is the Laravel-shaped failure, not an 8.7 notice.
What would bite on a self-hosted VPS
Caption: php -v first. Hash 80 bytes. Verify 80, first 72, and 72 plus junk. All true means I am still on silent truncation. Then I choose: refuse concat, set BCRYPT_LIMIT=72, or switch HASH_DRIVER.
First bite: the socket, not the RFC. I can prove truncation in CLI 8.3.6 and leave Nginx on an 8.4 pool I never checked. Cost 10 vs cost 12 is a password_needs_rehash story. The 72-byte window is the same on every current PASSWORD_BCRYPT.
Second bite: BCRYPT_LIMIT=72 without a form rule. Illuminate throws InvalidArgumentException. The user sees a 500. I would add a strlen() rule on register before I flip the env, and I would not bump rounds the same afternoon.
Third bite: argon2id is not a panel checkbox. password_algos() listed argon2i,argon2id on this archive 8.3. Laravel’s default argon memory is 65536 KiB. Tight on a 1 GB VPS that also runs MySQL and two FPM pools. Distro Ubuntu 24.04 PHP is still 8.3.6. I would not invent that apt shipped 8.7, or that PASSWORD_BCRYPT_SHA256 arrived via unattended-upgrades. config.platform.php stays on the FPM I serve.
Ten days of drafts, zero votes
Caption: Changelog 27 Sep. Internals announce 29 Sep. Internals recap 1–2 Oct. bcrypt_sha256 draft 4–5 Oct. 8.6.0RC3 on 8 Oct. I am writing 9 Oct. Vote tables are empty. GA for 8.6 is still 19 Nov 2026.
Honest target this week: Laravel 13 on the FPM I already have, BCRYPT_LIMIT either null or 72 on purpose, no pepper concatenated into bcrypt. Next calendar check is 8.6 GA on 19 Nov 2026 — still not this RFC.
Are you on this in production?
If FPM already hashes the raw password with bcrypt, BCRYPT_LIMIT is still null, and you never prepend a nonce or pepper into Hash::make, you are on this the boring way: the 72-byte window exists and you are not stuffing it. If CLI and the Nginx socket disagree, or config:cache still has HASH_DRIVER=bcrypt after you edited .env, or the host will not give you argon2id in password_algos(), you are not. Shared hosting that will not let you set BCRYPT_LIMIT is the fourth camp. I did not flip a production hasher tonight. I want to know which camp you are in.
Did you hit the same wall?
I would get stuck on verify(80) === true and verify(first 72) === true with no notice, or on InvalidArgumentException: Value is too long to hash the afternoon I set BCRYPT_LIMIT=72 without a byte-length form rule. Did you hit the same thing, or a different one — Password::max(72) letting a 40-character CJK passphrase through, a cached hashing config, argon2 missing from php -m, a panel that will not give you that pool? Tell me in the comments. I read them.
References
- PHP
password_hashmanual (72-byte bcrypt caution; retrieved 9 Oct 2026): https://www.php.net/manual/en/function.password-hash.php - php.watch
PASSWORD_DEFAULTstill"2y"(retrieved 9 Oct 2026): https://php.watch/codex/PASSWORD_DEFAULT - PHP 8.4 bcrypt default cost 10 → 12: https://php.watch/versions/8.4/password_hash-bcrypt-cost-increase
- RFC
bcrypt_max_password_length(changelog 2026-09-27; proposed 8.7 deprecation / 8.8ValueError; vote 0–0–0): https://wiki.php.net/rfc/bcrypt_max_password_length - php.internals RFC announce, 29 Sep 2026 11:33:05 UTC: https://news-web.php.net/php.internals/132689
- php-src PR #23076 (72-byte handling): https://github.com/php/php-src/pull/23076
- This Week in PHP Internals, Oct 1 2026 (dev.to; Discourse 2 Oct 2026): https://dev.to/projektgopher/this-week-in-php-internals-oct-1-2026-1f24
- RFC
bcrypt_sha256(changelog 2026-10-04; proposed next PHP 8.x / 8.7;PASSWORD_DEFAULTunchanged): https://wiki.php.net/rfc/bcrypt_sha256 - php.internals
PASSWORD_BCRYPT_SHA256announce, 5 Oct 2026 09:20:29 UTC: https://news-web.php.net/php.internals/132784 - php-src PR #24073 (open as of 6 Oct 2026): https://github.com/php/php-src/pull/24073
- php.watch PHP 8.6 releases (8.6.0RC3 8 Oct 2026; GA 19 Nov 2026; retrieved 9 Oct 2026): https://php.watch/versions/8.6/releases
- Laravel 13 hashing docs: https://laravel.com/docs/13.x/hashing
- Laravel 13.x
config/hashing.php(HASH_DRIVERbcrypt,BCRYPT_LIMITnull): https://github.com/laravel/framework/blob/13.x/config/hashing.php - Laravel 13.x
BcryptHasher(limit throwsInvalidArgumentException): https://github.com/laravel/framework/blob/13.x/src/Illuminate/Hashing/BcryptHasher.php - laravel/framework#54509, merged 10 Feb 2025: https://github.com/laravel/framework/pull/54509
- FreshRSS GHSA-pcq9-mq6m-mvmp, published 8 Mar 2026, CVE-2025-68402 (edge only): https://github.com/FreshRSS/FreshRSS/security/advisories/GHSA-pcq9-mq6m-mvmp
- PHP bug #67653 (silent bcrypt truncation, referenced by the RFC): https://bugs.php.net/bug.php?id=67653