Hello,
I have drafted a new RFC: https://wiki.php.net/rfc/bcrypt_max_password_length
The proposal is to throw a ValueError when a password longer than 72 characters is passed to password_hash and bcrypt is used. The current behavior is that the password is silently truncated and only the first 72 characters are hashed.
The goal is to prevent severe security vulnerabilities that are the result of this silent truncation. This will primarily impact applications that pass something else than just the user's password to password_hash. Please let me know what you think!
Regards,
Sjoerd
On Tue, 29 Sept 2026 at 12:35, Sjoerd Langkemper
sjoerd-php@linuxonly.nl wrote:
Hello,
I have drafted a new RFC: https://wiki.php.net/rfc/bcrypt_max_password_length
The proposal is to throw a ValueError when a password longer than 72 characters is passed to password_hash and bcrypt is used. The current behavior is that the password is silently truncated and only the first 72 characters are hashed.
The goal is to prevent severe security vulnerabilities that are the result of this silent truncation. This will primarily impact applications that pass something else than just the user's password to password_hash. Please let me know what you think!
Regards,
Sjoerd
Checking that the input is less than 72 bytes long is the
application's responsibility, not the algorithm's. The algorithm is
designed in a way that you don't need to worry about too-long
passphrases as an application developer. Vulnerabilities come from
developers trying to outsmart the algorithm by modifying passwords
before hashing or by providing something other than a
password/passphrase as an input. No change to the algorithm is going
to prevent that.
By adding a ValueError which only fires when the input is too long,
you are introducing a silent failure vector that is difficult to catch
or test for. If an application doesn't limit user password length to
72 bytes, there will be users that will try such passwords and the
application will crash for them instead of working correctly as
before. And since an overlong password is an acceptable input, that
error is not going to help neither the user or the developer.
Such a limitation would only help in really egregious cases of misuse,
when a developer prepends an almost 72-byte string to a password or
passes something other than a password as input. Both should be caught
in a code review by a senior developer, not runtime.
Hi
[…]
I agree with that in full.
Best regards
Tim Düsterhus
The proposal is to throw a ValueError when a password longer than 72 characters is passed to password_hash and bcrypt is used.
Checking that the input is less than 72 bytes long is the
application's responsibility, not the algorithm's.
Thank you for your feedback. It is clear to me that you oppose my proposal as is. Do you have a suggestion for improvement? Something I can change that would restrict misuse of password_hash but would have your approvement?
there will be users that will try such passwords and the
application will crash for them instead of working correctly as
before.
Yes, this can happen. This is an intentional tradeoff: my proposed change makes the situation more secure, but could crash applications in some situations. I described in the RFC that users rarely/never use passwords longer than 72 characters, so I think this is acceptable.
Such a limitation would only help in really egregious cases of misuse,
when a developer prepends an almost 72-byte string to a password or
passes something other than a password as input. Both should be caught
in a code review by a senior developer, not runtime.
The FreshRSS case is interesting here. Each change was reviewed and seemed secure, but the combination resulted in authentication bypass. https://pentesterlab.com/blog/freshrss-bcrypt-truncation-auth-bypass
Regards,
Sjoerd
Hi
there will be users that will try such passwords and the
application will crash for them instead of working correctly as
before.Yes, this can happen. This is an intentional tradeoff: my proposed
change makes the situation more secure, but could crash applications in
some situations. I described in the RFC that users rarely/never use
passwords longer than 72 characters, so I think this is acceptable.
The limit is 72 bytes, not 72 characters. This is a meaningful
difference: It means that users might be presented an error for
non-ASCII passwords.
An 8-word Diceware password comes in at 100 bits of entropy and is
roughly around 72 bytes in length. A 9-word password would exceed the
limit, but the extra entropy above 100 bits is not really meaningful
with regard to security, so just ignoring that extra word is fine.
Such a limitation would only help in really egregious cases of misuse,
when a developer prepends an almost 72-byte string to a password or
passes something other than a password as input. Both should be caught
in a code review by a senior developer, not runtime.The FreshRSS case is interesting here. Each change was reviewed and
seemed secure, but the combination resulted in authentication bypass.
https://pentesterlab.com/blog/freshrss-bcrypt-truncation-auth-bypass
The specified authentication protocol with “client side hashing” is a
classic case of “rolling your own crypto” and it exposes the BCrypt salt
to the client, likely to everyone who asks, since it clearly is
pre-auth. I disagree with calling that part “seemingly secure”.
Even the updated nonce-generation, while better than before due to the
use of the CSPRNG, includes needless security theater. The
random_bytes() is what makes the nonce secure. Neither the system salt
nor the username needs to be included and the SHA-256 hash just acts as
a PRF, so saying “SHA-256 is stronger than SHA-1” is correct, but also
utterly meaningless.
The write-up summarizes it well: “Over-engineering can hurt security”.
The issue was not caused by the BCrypt truncation, it was caused my
multiple problematic decisions. The latter includes the BCrypt
truncation, but that ship has sailed and trying to enforce limits that
BCrypt itself does not is making the situation worse.
Best regards
Tim Düsterhus
By adding a ValueError which only fires when the input is too long,
you are introducing a silent failure vector that is difficult to catch
or test for.
On the contrary, it turns a silent failure into a noisy one, prompting the developer to take action.
If an application doesn't limit user password length to
72 bytes, there will be users that will try such passwords and the
application will crash for them instead of working correctly as
before.
Such a system was not working correctly before - it was accepting passwords that it could not verify later, and consequently accepting logins which did not match the user's intended password.
And since an overlong password is an acceptable input
Why is it an acceptable input? If the algorithm can't correctly hash that input, why should it tell the user it has done so?
It would be a problem if users who have already set passwords which they intended to be longer than 72 bytes are prevented from logging in, but the proposal covers that by leaving password_verify unchanged.
Both should be caught in a code review no a senior developer, not runtime.
If every PHP login implementation was reviewed by an expert senior developer, we would not need the password_* API in the first place. The value of this API is that it makes doing the right thing easy, so that you don't need to be an expert in the underlying algorithms to use it safely.
I support the RFC as currently proposed.
Rowan Tommins
[IMSoP]
Hi
On 29 September 2026 13:04:21 BST, Kamil Tekiela tekiela246@gmail.com
wrote:By adding a ValueError which only fires when the input is too long,
you are introducing a silent failure vector that is difficult to catch
or test for.On the contrary, it turns a silent failure into a noisy one, prompting
the developer to take action.
There is no silent failure here. BCrypt acts according to its
specification.
[…] it was accepting passwords that it could not verify later […]
I assume it is ambiguous phrasing, but to be clear: Any passwords
accepted by password_hash() will verify with password_verify().
And since an overlong password is an acceptable input
Why is it an acceptable input? If the algorithm can't correctly hash
that input, why should it tell the user it has done so?It would be a problem if users who have already set passwords which
they intended to be longer than 72 bytes are prevented from logging in,
but the proposal covers that by leaving password_verify unchanged.
The login will start to fail when the password is being rehashed
(password_needs_rehash()) due to a change in the algorithm parameters,
such as when increasing the (default) BCrypt cost.
Both should be caught in a code review no a senior developer, not
runtime.If every PHP login implementation was reviewed by an expert senior
developer, we would not need the password_* API in the first place. The
value of this API is that it makes doing the right thing easy, so that
you don't need to be an expert in the underlying algorithms to use it
safely.
The API is safe if you pass a “password” to it (as the name indicates).
The issues described in the RFC were caused by folks passing something
that is not a password.
Best regards
Tim Düsterhus
The API is safe if you pass a “password” to it (as the name indicates).
The issues described in the RFC were caused by folks passing something
that is not a password.
Exactly, and my proposal aims to throw a ValueError in those cases, indicating that it is a programming error to pass anything other than password. The tradeoff is that actual passwords longer than 72 bytes are no longer supported. I think that is acceptable. I have gathered data showing that such passwords are exceedingly rare. Also, if users genuinely need longer passwords, it is also not defensible to silently truncate the password.
The login will start to fail when the password is being rehashed
(password_needs_rehash()) due to a change in the algorithm parameters
That is a good point, I had not considered that. Thanks.
Regards,
Sjoerd
Exactly, and my proposal aims to throw a ValueError in those cases, indicating that it is a programming error to pass anything other than password. The tradeoff is that actual passwords longer than 72 bytes are no longer supported. I think that is acceptable.
The function signature already says it will accept a string value,
and it is already working under this contract. Limiting it to 72 bytes
does not ensure the passed string is a password, and it is merely an
implementation detail for bcrypt. The php.net manual page for
password_hash also already mentions the 72-byte truncation behavior.
Passing an invalid hashing algorithm, unsupported algorithm options,
RNG failures, etc., are indeed programmer/program errors, and it
already throws hard exceptions/errors in those cases.
I wouldn't also characterize the two vulnerabilities mentioned in the
RFC (in other software) as related. In PHP, the salt is now (PHP 7+)
always generated automatically, and the password_hash() output is not
limited to a certain length. While I agree that vulnerable software
could be written around bcrypt's 72-byte behavior, it would not be a
strong relation to PHP.
Thank you,
Ayesh.
Hi all,
I echo Rowan's response and find some of the opposing arguments quite
strange:
The limit is 72 bytes, not 72 characters. This is a meaningful
difference: It means that users might be presented an error for
non-ASCII passwords.
That's a good argument against the current silent truncation behavior,
because:
- It also means effective length can be at least halved, depending on the
character set in use. - Some character sets (most prominently UTF-16) specifically use NUL bytes
inside character codes.
Not accounting for these issues violates modern security standards. I could
list multiple, but in all honestly they're universal consensus on what's
already established by NIST, so see that:
https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63B-4.pdf#subsubsection.3.1.1
An 8-word Diceware password comes in at 100 bits of entropy and is
roughly around 72 bytes in length. A 9-word password would exceed the
limit, but the extra entropy above 100 bits is not really meaningful
with regard to security, so just ignoring that extra word is fine.
It is inappropriate for us to make such a decision for end users. Doubly so
when the user would be completely unaware of it, which is what happens when
you have silent truncation.
I've personally witnessed many lack-of-MFA "mitigation" strategies that
rely on absurdly-long passwords, and the people involved in those decisions
didn't even include developers. GRC types and over-confident IT admins
trying to reason about how to use off-the-shelf software. To account for
bcrypt's inherent limitation in this context would require people with very
specialized skills and superhuman foresight.
[... in response to the FreshRSS example ...]
The write-up summarizes it well: “Over-engineering can hurt security”.
The issue was not caused by the BCrypt truncation, it was caused my
multiple problematic decisions. The latter includes the BCrypt
truncation, but that ship has sailed and trying to enforce limits that
BCrypt itself does not is making the situation worse.
Bcrypt is the footgun that fired. Yes, the FreshRSS dev(s) made many wrong
decisions and clearly didn't understand what they were doing. Okta also
fell into a trap and that's a company which no doubt most people here would
trust on security matters. That in itself demonstrates why
truncate-at-72-bytes is a bcrypt deficiency that we should want to fix -
the number of people who truly understand the problem space is extremely
small.
The RFC is rather roughly-argumented as is (I'd be happy to help Sjoerd
with that) and there's plenty of room to debate on BC break grounds, but
the problem it tries to address is spot on.
P.S. I've quoted Tim, but only because he's been most thorough and
well-worded; other responses have made even less logical sense.
Cheers,
Andrey.
Hi
The limit is 72 bytes, not 72 characters. This is a meaningful
difference: It means that users might be presented an error for
non-ASCII passwords.That's a good argument against the current silent truncation
behavior,
because:
- It also means effective length can be at least halved, depending on
the
character set in use.- Some character sets (most prominently UTF-16) specifically use NUL
bytes
inside character codes.Not accounting for these issues violates modern security standards. I
could
list multiple, but in all honestly they're universal consensus on
what's
already established by NIST, so see that:
https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63B-4.pdf#subsubsection.3.1.1
You are correct in that the NIST standard specifies that:
- Verifiers SHALL request the password to be provided in full (not a
subset of it) and SHALL verify the entire submitted password (e.g., not
truncate it)
However: The NIST standard applies to the “Verifier” (i.e. the
application), not to the primitive. While password_hash() is certainly
intended to be usable as-is, an application requiring strict compliance
with all SHALLs and SHOULDs of the NIST standard already needs to
perform some steps that cannot be solved by password_hash(), such as
verifying that known-insecure passwords cannot be used. And when trying
for pedantic compliance with NIST you cannot use BCrypt at all, since
BCrypt technically is not in the list of NIST-approved hashing
functions. For that you need an approved hash function, and my latest
knowledge is that neither BCrypt nor Argon2’s BLAKE2b primitive are
acceptable. In practice this means using hash_pbkdf2().
If we ignore all that, implementing the RFC would mean that BCrypt
hashing with PHP would start to deviate from a combination of two
different requirements:
- Verifiers and CSPs SHOULD permit a maximum password length of at
least 64 characters.
and
- Verifiers and CSPs SHOULD accept Unicode [ISO/ISC 10646] characters
in passwords. Each Unicode code point SHALL be counted as a single
character when evaluating password length.
The RFC would thus trade a SHALL violation for a SHOULD deviation that
affects exactly the users using non-ASCII character sets.
And while the effective length is reduced for e.g. CJK characters, they
are also much more information dense than ASCII characters. An English
word taking up 5-7 bytes can often be represented as a single 3-byte CJK
character. Of course the RFC also does nothing about this: Instead of
reducing the effective length, inputs exceeding “the effective length”
are rejected, forcing the user to pick a shorter password. The
achievable security ceiling is the same - and high enough.
The NUL byte point is moot, because NUL bytes are already rejected
during hashing. For that I agree with the ValueError / check, since the
truncation on NUL is much more catastrophic - and because actually
including a NUL in a legit user-provided password is much more
complicated than exceeding a 72 byte limit. In practice the Internet has
also converged on “just using UTF-8”.
It is inappropriate for us to make such a decision for end users.
Doubly so
when the user would be completely unaware of it, which is what happens
when
you have silent truncation.I've personally witnessed many lack-of-MFA "mitigation" strategies that
rely on absurdly-long passwords, and the people involved in those
decisions
didn't even include developers. GRC types and over-confident IT admins
trying to reason about how to use off-the-shelf software. To account
for
bcrypt's inherent limitation in this context would require people with
very
specialized skills and superhuman foresight.
I did the math in a sibling email: Truncating absurdly-long passwords
still results in “you ain't gonna need it” amounts of entropy.
The RFC is rather roughly-argumented as is (I'd be happy to help Sjoerd
with that) and there's plenty of room to debate on BC break grounds,
but
the problem it tries to address is spot on.
I don't necessarily disagree with the BCrypt truncation being a problem,
but in this case the cure is worse than the disease.
Best regards
Tim Düsterhus
Hi Tim,
https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63B-4.pdf#subsubsection.3.1.1
You are correct in that the NIST standard specifies that:
- Verifiers SHALL request the password to be provided in full (not a
subset of it) and SHALL verify the entire submitted password (e.g., not
truncate it)However: The NIST standard applies to the “Verifier” (i.e. the
application), not to the primitive. Whilepassword_hash()is certainly
intended to be usable as-is, an application requiring strict compliance
with all SHALLs and SHOULDs of the NIST standard already needs to
perform some steps that cannot be solved bypassword_hash(), such as
verifying that known-insecure passwords cannot be used. And when trying
for pedantic compliance with NIST you cannot use BCrypt at all, since
BCrypt technically is not in the list of NIST-approved hashing
functions. For that you need an approved hash function, and my latest
knowledge is that neither BCrypt nor Argon2’s BLAKE2b primitive are
acceptable. In practice this means usinghash_pbkdf2().
I pointed to NIST SP 800-63 as the root source, but not the only
standard. If I pointed to the OWASP ASVS for example, most of what you
wrote would be irrelevant, but my basis - the correct implication of the
bytes vs characters distinction - is still proven. All of the math
arguments you've brought up previously are eroded by that, and I really
must say it - deceiving developers and end-users about behavior is not a
math problem to begin with.
What you're technically correct on is the Verifier vs "primitive" (not
really a primitive in the true sense of that term). However, obfuscating
bcrypt's deficiencies is actively reducing developers' capacity to
compensate for that, and leaving them with a false sense of security.
If we ignore all that, implementing the RFC would mean that BCrypt
hashing with PHP would start to deviate from a combination of two
different requirements:
- Verifiers and CSPs SHOULD permit a maximum password length of at
least 64 characters.and
- Verifiers and CSPs SHOULD accept Unicode [ISO/ISC 10646] characters
in passwords. Each Unicode code point SHALL be counted as a single
character when evaluating password length.The RFC would thus trade a SHALL violation for a SHOULD deviation that
affects exactly the users using non-ASCII character sets.
I don't see an added SHALL violation; the RFC is only making an
already-present limitation visible.
And while the effective length is reduced for e.g. CJK characters, they
are also much more information dense than ASCII characters. An English
word taking up 5-7 bytes can often be represented as a single 3-byte CJK
character. Of course the RFC also does nothing about this: Instead of
reducing the effective length, inputs exceeding “the effective length”
are rejected, forcing the user to pick a shorter password. The
achievable security ceiling is the same - and high enough.
Might be true for CJK; not true for Cyrilic, Greek - halved in length and
strength no matter how you look at it. And I'm not mentioning others simply
because I'm not familiar with them, but there are multiple.
The NUL byte point is moot, because NUL bytes are already rejected
during hashing. For that I agree with the ValueError / check, since the
truncation on NUL is much more catastrophic - and because actually
including a NUL in a legit user-provided password is much more
complicated than exceeding a 72 byte limit. In practice the Internet has
also converged on “just using UTF-8”.
I admit I keep forgetting that the NUL byte thing was already patched. The
point isn't entirely moot still, but not worth arguing third-order effects
and we have plenty of other disagreements, so be it.
I don't necessarily disagree with the BCrypt truncation being a problem,
but in this case the cure is worse than the disease.
As long as you recognize the problem, I don't think we'd be far apart. I
absolutely recognize the BC break as a major concern that might take higher
precedence. Until now though, your position has been a lot closer to "no
problem here at all" than "I draw the line at breaking existing
applications".
Cheers,
Andrey.
Hi
I pointed to NIST SP 800-63 as the root source, but not the only
standard. If I pointed to the OWASP ASVS for example, most of what you
wrote would be irrelevant, but my basis - the correct implication of
the
bytes vs characters distinction - is still proven. All of the math
arguments you've brought up previously are eroded by that, and I really
must say it - deceiving developers and end-users about behavior is not
a
math problem to begin with.
Even with a non-ASCII 2-byte per character diceware password you are at
roughly 50 bits of entropy in the 72 bytes, which makes the math go from
“an attacker that started at the big bang still wouldn't have cracked
it” to “a dedicated attacker with 20_000 GPUs at hand might be able to
crack a single password within a year” (given the current default BCrypt
cost of 12 and 1.5kH/s/GPU which is within the range you mentioned in
the Argon2 thread). And as further elaborated below, the RFC does not
change that ceiling and turns the “not a math problem” into a usability
problem.
The RFC would thus trade a SHALL violation for a SHOULD deviation that
affects exactly the users using non-ASCII character sets.I don't see an added SHALL violation; the RFC is only making an
already-present limitation visible.
I said that it would be trading a SHALL for a SHOULD, not that it would
add new SHALL violations. This is theoretically better when going purely
based on standard reading, but does not improve the real world
situation.
Might be true for CJK; not true for Cyrilic, Greek - halved in length
and
strength no matter how you look at it. And I'm not mentioning others
simply
because I'm not familiar with them, but there are multiple.
The conclusion remains the same: The user picks a shorter password and
you are back to square 1. And of course telling the user “your password
may be at most 72 bytes in length” when they don't know what bytes are
and why some characters are more than 1 byte (so they can't even count
the number of bullets in the password input to check if their password
fits) will just lead to frustrated users picking much weaker passwords
instead.
I don't necessarily disagree with the BCrypt truncation being a
problem,
but in this case the cure is worse than the disease.As long as you recognize the problem, I don't think we'd be far apart.
I
absolutely recognize the BC break as a major concern that might take
higher
precedence. Until now though, your position has been a lot closer to
"no
problem here at all" than "I draw the line at breaking existing
applications".
Your interpretation is not quite correct. My position is roughly “it
sure would be better if BCrypt wouldn't have the truncation limit, but
that ship has sailed”. My main issue with this proposal is not the
“breaking existing applications” bit, but “forcing applications to
expose the byte limitation to the non-technical end user”. The RFC is
turning a theoretical security issue (for genuine passwords) into actual
usability issues for non-technical users who learned “longer = better”.
Best regards
Tim Düsterhus
Even with a non-ASCII 2-byte per character diceware password you are at roughly 50 bits of entropy in the 72 bytes, which makes the math...
I don't think there maths and entropy etc, is the major problem for me with this proposal.
The problem for me is that this a scary BC break.
If a user of a site has a silly long password now, they can still login. If this changes to an Exception, then they no longer can, without intervention from a site owner, for whom there is now BC break in the language throwing random new exceptions based on user input.
I'm going to be -1 on this one.
cheers
Derick
The problem for me is that this a scary BC break.
Yes. I think the BC break is worth it, but I understand how opinions may differ on this.
If this changes to an Exception, then they no longer can [login]
Sort of. I intentionally only proposed changing password_hash and not password_verify. However, password_hash may still be used in the login flow, so login may fail for long passwords but this depends on the application logic.
Is there anything you could think of that would improve the situation but would potentially have your approval? First switch the default password hash from bcrypt to something else? Increase the length at which an exception is thrown? Not an exception but a warning?
Regards,
Sjoerd
Hi
Is there anything you could think of that would improve the situation
but would potentially have your approval? First switch the default
password hash from bcrypt to something else? Increase the length at
which an exception is thrown? Not an exception but a warning?
PHP Warnings are commonly converted to Exceptions by a throwing error
handler, particularly when using one of the major frameworks. The native
ErrorException exists just for this purpose. For a significant number of
applications emitting a warning is equivalent to throwing an Exception.
Regarding the length increase my understanding is that the argument in
favor of the RFC is that “silently truncating inputs is a security
issue”, so increasing the length would keep the security issue, while
also keeping the usability issue.
For PASSWORD_BCRYPT the only acceptable option to me is “leave it
alone”. Generally speaking user-provided passwords must never throw
(with the possible exception of ASCII control characters, including
NUL).
To provide a constructive suggestion: Borrowing passlib’s
bcrypt-sha256
(https://passlib.readthedocs.io/en/stable/lib/passlib.hash.bcrypt_sha256.html)
algorithm (including the output format) might be a valid option for a
well-defined pre-hashing solution.
In fact even your RFC gets pre-hashing wrong by specifying that folks
should pass the raw prehash to BCrypt, which would result in a
ValueError for 12% of users (for SHA-256) due to a NUL byte appearing in
the raw output - and catastrophic NUL truncation if PHP would not be
checking for NUL. This is the kind of fragile workaround that I
mentioned in my reply to Rowan
(https://news-web.php.net/php.internals/132734).
Best regards
Tim Düsterhus
To provide a constructive suggestion: Borrowing passlib’s
bcrypt-sha256
algorithm (including the output format) might be a valid option for a
well-defined pre-hashing solution.
Yes, having another password hashing algorithm that does not truncate (like bcrypt) and is included by default (unlike argon2) would be great.
However, I was under the impression that this was problematic, seeing the lack of progress on yescrypt. Perhaps bcrypt-sha256 is different since we already have both fundamental functions built in?
yescrypt PR: https://github.com/php/php-src/pull/16452
Regards,
Sjoerd
Hi
However, I was under the impression that this was problematic, seeing
the lack of progress on yescrypt. Perhaps bcrypt-sha256 is different
since we already have both fundamental functions built in?
The problems are with including extra libraries. A minimal build of PHP
should be possible without installing any extra libraries, which means
that libraries are vendored into php-src as necessary. This comes with
the usual problems of vendoring, such as keeping libraries up to date.
Yescrypt likely doesn't provide enough benefit for the extra maintenance
effort.
For bcrypt-sha256 this situation is different, since all the necessary
primitives are already always-available.
For reference to the other readers, Sjoerd already opened a PR with an
implementation: https://github.com/php/php-src/pull/24073. There are
some technical nits, but I don't see anything fundamentally wrong with
it.
Best regards
Tim Düsterhus
Hi again,
Before I go into a more lengthy response, a quick reply to Robert, because
his question keeps popping up outside of this RFC too:
A developer might ask why the application is doing this, but the RFC
asks why is PHP doing this? Is the function named password_verify going
to verify the password or not?
password_verify() remains unchanged on purpose, and that's a good thing.
There is some natural compulsion to make it consistent with
password_hash(), but it's a trap that would break old passwords for no
benefit. I would oppose any such change.
Even with a non-ASCII 2-byte per character diceware password you are at
roughly 50 bits of entropy in the 72 bytes, which makes the math go from
“an attacker that started at the big bang still wouldn't have cracked
it” to “a dedicated attacker with 20_000 GPUs at hand might be able to
crack a single password within a year” (given the current default BCrypt
cost of 12 and 1.5kH/s/GPU which is within the range you mentioned in
the Argon2 thread). And as further elaborated below, the RFC does not
change that ceiling and turns the “not a math problem” into a usability
problem.
See, but you keep claiming it's a usability problem while defending it as a
math problem, and being very selective about it too - you alone keep
bringing up diceware. It's not the only plausible way to exceed the limit.
I don't see an added SHALL violation; the RFC is only making an
already-present limitation visible.
I said that it would be trading a SHALL for a SHOULD, not that it would
add new SHALL violations. This is theoretically better when going purely
based on standard reading, but does not improve the real world
situation.
Sorry about my imprecise wording, but I did follow the logical consequence
of the trade to mean that, in order to satisfy SHOULDs you'd be violating a
SHALL. Maybe you meant it the other way around ... Unimportant, just wanted
to clear the air.
Might be true for CJK; not true for Cyrilic, Greek - halved in length
and
strength no matter how you look at it. And I'm not mentioning others
simply
because I'm not familiar with them, but there are multiple.The conclusion remains the same: The user picks a shorter password and
you are back to square 1. And of course telling the user “your password
may be at most 72 bytes in length” when they don't know what bytes are
and why some characters are more than 1 byte (so they can't even count
the number of bullets in the password input to check if their password
fits) will just lead to frustrated users picking much weaker passwords
instead.
That's sweeping a lot of details under the rug.
- Saying that users will choose weaker passwords, while at the same time
standing on the argument that length difference at that ceiling is
irrelevant. I can see what you meant non-maliciously, but I'm sure you too
can see how that's having it both ways and not a fair representation. - The average user, for whom we have observations and can make some
assumptions about, is not the one choosing extreme-length passwords. The
limit will be hit either by people who have an affinity for security OR by
users who are victims of problems constructed upstream from them (such as
prefixes added by developers and corporate IT staff, for reasons right or
wrong). In the latter case, raising an error is the only way to make these
these upstream issues visible. - While a poor way to construct passwords, some users will choose a
constant prefix and then a suffix based on the domain name they're
accessing (or functionally equivalent unique identifier). If the prefix is
long enough, silent truncation is completely eliminating the user's ability
to know that they no longer have unique passwords (however flawed their
method was), in turn making them vulnerable to more types of attacks. Note
that passwords get exposed in ways other than cracking hashes, like
accidental (or even intentional) logging, so that's not a prerequisite. - We are (for obvious reasons) looking at the whole thing from a
server-side perspective, but where any burden is shifted onto the end user,
there is a client side and input field limits are very much a thing.
How the 1-byte or 2-byte characters disctinction is handled, if at all
possible, is indeed a real problem. Now we have progress!
Your interpretation is not quite correct. My position is roughly “it
sure would be better if BCrypt wouldn't have the truncation limit, but
that ship has sailed”. My main issue with this proposal is not the
“breaking existing applications” bit, but “forcing applications to
expose the byte limitation to the non-technical end user”. The RFC is
turning a theoretical security issue (for genuine passwords) into actual
usability issues for non-technical users who learned “longer = better”.
Fair enough, though we'll have to agree to disagree on the ship sailed
argument.
My expectation is that developers who are “alerted” to the limitation
will just go with whatever solution makes the error message go away the
quickest instead of trying to understand the impact, leaving the overall
system in a less secure state.
Silent truncation by PHP itself is no different in principle. Dressing up
the same fallacy because we think we know better than the affected parties
is just putting lipstick on a pig.
To provide a constructive suggestion: Borrowing passlib’s
bcrypt-sha256
(
https://passlib.readthedocs.io/en/stable/lib/passlib.hash.bcrypt_sha256.html)algorithm (including the output format) might be a valid option for a
well-defined pre-hashing solution.
I am actually preparing something based on the same principle, but didn't
want to bring it up prematurely. I think this RFC has merit on its own and
have seen way too many proposals be killed on the promise of something else
in the uncertain future.
Cheers,
Andrey.
If a user of a site has a silly long password now, they can still login. If this changes to an Exception, then they no longer can, without intervention from a site owner, for whom there is now BC break in the language throwing random new exceptions based on user input.
I agree with Derick's concerns. This change could affect users who chose
long passwords because they believed they would be more secure. I don't
believe those users should be 'affected.
I support changing the default to argon2, but this change will be a -1
from me as well.
--
Regards,
Jordi Kroon
On 30 September 2026 22:26:21 BST, "Tim Düsterhus" tim@bastelstu.be
wrote:If a user of a site has a silly long password now, they can still login.
If this changes to an Exception, then they no longer can, without
intervention from a site owner, for whom there is now BC break in the
language throwing random new exceptions based on user input.I agree with Derick's concerns. This change could affect users who chose
long passwords because they believed they would be more secure. I don't
believe those users should be 'affected.
Please folks, read the RFC before parroting this line.
The RFC only proposes changing password_hash(), NOT password_verify().
If a hash already exists from a truncated password, it will continue to
validate. The only time this would raise the exception or error is when
hashing, which will typically be done once, when a user registers, or
chooses to change their password. Having an error condition here forces the
application developer to address the truncation issue when storing new
hashes only, and won't invalidate existing user hashes and login attempts.
--
Matthew Weier O'Phinney
mweierophinney@gmail.com
https://mwop.net/
he/him
On Thu, 1 Oct 2026 at 18:15, Matthew Weier O'Phinney
mweierophinney@gmail.com wrote:
If a hash already exists from a truncated password, it will continue to validate. The only time this would raise the exception or error is when hashing, which will typically be done once, when a user registers, or chooses to change their password. Having an error condition here forces the application developer to address the truncation issue when storing new hashes only, and won't invalidate existing user hashes and login attempts.
Small correction. Rehashing is commonly done during login, not registration.
Hi
The tradeoff is that actual passwords longer than 72 bytes are no
longer supported. I think that is acceptable. I have gathered data
showing that such passwords are exceedingly rare.
Even something that is “exceedingly rare” has a non-zero chance of
occurring. And at the scale of PHP’s deployment it will occur at
non-trivial numbers.
Also, if users genuinely need longer passwords, it is also not
defensible to silently truncate the password.
Mathematically users don't need longer passwords, but they might
feel they need longer passwords, because they are not aware of the
maths behind it [1]. This is a user experience concern: If the user
feels the need a long password to remain secure then telling them that
their password is “too strong” will not help the user feel secure and
they might be worried that the website is insecure for unfounded
reasons.
Best regards
Tim Düsterhus
[1] I'm seeing this all the time in code bases where developers call
random_bytes() with an unreasonable output length, because “longer is
better” and then also include “retry loops” in case the output still
collided. 16 bytes / 128 bits matches the key size of AES-128, which is
considered secure and the algorithm of choice for TLS server operators
who care about performance rather than big numbers, this includes Google
and Cloudflare who negotiate AES-128 rather than AES-256.
There is no silent failure here. BCrypt acts according to its specification.
I think we're coming at this from opposite directions: you're looking at bcrypt as an algorithm, and saying that it has a well-defined behaviour; I am looking at password verification as a task, and saying that bcrypt performs that task incorrectly for long inputs.
[…] it was accepting passwords that it could not verify later […]
I assume it is ambiguous phrasing, but to be clear: Any passwords accepted by
password_hash()will verify withpassword_verify().
You're right, it was ambiguous - I was talking about false positives, rather than false negatives. That is, a correctly entered password will be accepted, but an incorrectly entered password may also be accepted.
Another failure state would be password change policies which mandate "must not match previous X passwords". A user would reasonably expect that adding or changing something at the end of a long passphrase would be allowed; but since the passphrase was silently truncated, their change would be rejected.
The login will start to fail when the password is being rehashed
(password_needs_rehash()) due to a change in the algorithm parameters,
such as when increasing the (default) BCrypt cost.
This is a good point, but I don't think it's a fatal problem. The affected applications are the same ones liable to see the error for new passwords, and the fix is the same: reject or explicitly truncate over-length inputs.
In fact, I think it would make sense to add a Warning to password_verify and password_needs_rehash, to prompt developers to check their implementation there as well.
There's actually another rehash edge case: the developer switches the preferred algorithm from bcrypt to argon2, and the user enters more than 72 bytes at login. The additional bytes won't be verified against the old hash, so might include a typo; but the full input will be used in the new hash, so the user will be locked out unless they can reproduce that typo.
The API is safe if you pass a “password” to it (as the name indicates). The issues described in the RFC were caused by folks passing something that is not a password.
As you pointed out yourself, a real user password could exceed the limit if using a multibyte encoding, or a diceware phrase.
Such passwords should be rejected, not silently corrupted by an implementation detail of the underlying hash algorithm.
Rowan Tommins
[IMSoP]
On Wed, 30 Sept 2026 at 14:39, Rowan Tommins [IMSoP]
imsop.php@rwec.co.uk wrote:
This is a good point, but I don't think it's a fatal problem. The affected applications are the same ones liable to see the error for new passwords, and the fix is the same: reject or explicitly truncate over-length inputs.
Truncating passwords is just as bad or even worse. The user will have
no idea that anything changed, and the developer has extra unnecessary
code. Rejecting them helps nobody either. When you reject them, the
user needs to truncate it without understanding why. No added security
there.
The security problems arise when developers are modifying passwords or
applying arbitrary restrictions. We improve security by not forcing
them to touch passwords.
In fact, I think it would make sense to add a Warning to password_verify and password_needs_rehash, to prompt developers to check their implementation there as well.
A warning is just a worse version of an exception. Besides, what are
we warning them about? The fact that a user mistyped a password? The
hash is already there, so comparing it with a password of arbitrary
length makes no difference.
Such passwords should be rejected, not silently corrupted by an implementation detail of the underlying hash algorithm.
The algorithm doesn't corrupt the passwords. The algorithm correctly
handles a password whether it's 8 bytes long or 80 bytes long. When
the password is too long, it just ignores the extraneous input, but
the security of the algorithm is the same.
On Wed, 30 Sept 2026 at 14:39, Rowan Tommins [IMSoP]
imsop.php@rwec.co.uk wrote:This is a good point, but I don't think it's a fatal problem. The affected applications are the same ones liable to see the error for new passwords, and the fix is the same: reject or explicitly truncate over-length inputs.
Truncating passwords is just as bad or even worse.
Truncating passwords is what happens right now. Making that choice explicit means the behaviour is stable, e.g. a rehash with a different algorithm won't add previously insignificant input to the hash. It also prompts the developer to tell users that it's happening, although we can't force them to do that.
Rejecting them helps nobody either. When you reject them, the
user needs to truncate it without understanding why. No added security
there.
The added security is not lying to users that they can set longer passwords.
A warning is just a worse version of an exception. Besides, what are
we warning them about? The fact that a user mistyped a password? The
hash is already there, so comparing it with a password of arbitrary
length makes no difference.
We are warning them that the input provided by the user may not actually match the password they provided at registration, because only part of it can be verified against the stored hash. And that the user should be advised of that fact.
The algorithm doesn't corrupt the passwords.
How is deleting part of the input not corrupting it?
The algorithm correctly
handles a password whether it's 8 bytes long or 80 bytes long. When
the password is too long, it just ignores the extraneous input
I find this statement completely baffling. It silently throws away input; I simply do not accept that as "correctly handling" it.
Imagine if it truncated at 7 bytes; would you still call that "correct handling", as long as it was documented behaviour?
the security of the algorithm is the same.
The algorithm treats 255 different 73-byte inputs as identical. A different algorithm would distinguish them. Are you really saying those are equally secure algorithms?
Rowan Tommins
[IMSoP]
On Wed, 30 Sept 2026 at 15:43, Rowan Tommins [IMSoP]
imsop.php@rwec.co.uk wrote:
On Wed, 30 Sept 2026 at 14:39, Rowan Tommins [IMSoP]
imsop.php@rwec.co.uk wrote:Rejecting them helps nobody either. When you reject them, the
user needs to truncate it without understanding why. No added security
there.The added security is not lying to users that they can set longer passwords.
We are not lying to them right now. They can set longer passwords.
It's just that longer doesn't mean it's more secure.
A warning is just a worse version of an exception. Besides, what are
we warning them about? The fact that a user mistyped a password? The
hash is already there, so comparing it with a password of arbitrary
length makes no difference.We are warning them that the input provided by the user may not actually match the password they provided at registration, because only part of it can be verified against the stored hash. And that the user should be advised of that fact.
It will still match. A hash of 72-byte long password and a hash of
73-byte long password where the first 72 bytes are the same will
match. https://3v4l.org/o7dJq#v8.5.11
The algorithm doesn't corrupt the passwords.
How is deleting part of the input not corrupting it?
Because the password is just as strong as it would be if we didn't
ignore the unnecessary bytes. It would be corrupted if the password
got weaker or changed in a way that it could no longer be verified
against its hash.
The algorithm correctly
handles a password whether it's 8 bytes long or 80 bytes long. When
the password is too long, it just ignores the extraneous inputI find this statement completely baffling. It silently throws away input; I simply do not accept that as "correctly handling" it.
Imagine if it truncated at 7 bytes; would you still call that "correct handling", as long as it was documented behaviour?
Obviously, yes. I don't understand your point.
the security of the algorithm is the same.
The algorithm treats 255 different 73-byte inputs as identical. A different algorithm would distinguish them. Are you really saying those are equally secure algorithms?
Yes, that is what I am saying. Password length doesn't affect how
secure the algorithm is. If bcrypt relied on password length for
security, it would be an inadequate password-hashing algorithm. A
1-byte password is just as secure as a 1000-byte one when we are
talking only about the algorithm's strength. 72 bytes is the input
space, and that dictates how secure the algorithm is. The length of
the actual input doesn't matter.
From the perspective of application security, developers may want to
establish the minimum length to prevent users from entering
easy-to-guess passwords, but that's to stop attackers from
brute-forcing them. The hash of "password" or "123" is just as secure
as the hash of the entire play of Hamlet.
A user who chooses a password longer than 72 bytes may feel like the
implementation is causing their password to be weaker because only the
first 72 bytes matter, but that's not the case.
We are not lying to them right now. They can set longer passwords.
No, they can't. They can input longer passwords, but that input is not stored.
If I encountered a database that truncates names to 10 bytes, I would describe it as not correctly handling longer names. Telling users that they can set longer names, but never actually storing them, would be lying.
It will still match. A hash of 72-byte long password and a hash of
73-byte long password where the first 72 bytes are the same will
match. https://3v4l.org/o7dJq#v8.5.11
Yes, and that is a false positive - the user provides the wrong password, and the system lets them in.
Because the password is just as strong as it would be if we didn't
ignore the unnecessary bytes. It would be corrupted if the password
got weaker or changed in a way that it could no longer be verified
against its hash.
Of course it gets weaker - it's shorter!
Imagine if it truncated at 7 bytes; would you still call that "correct handling", as long as it was documented behaviour?
Obviously, yes. I don't understand your point.
Then we are stuck. I can't imagine how "most of your password is silently thrown away" can be considered "correctly handled". It can be a known limitation, but from a user's point of view, it's very clearly not doing what they asked, which was to set a longer password.
Rowan Tommins
[IMSoP]
On Wed, 30 Sept 2026 at 17:40, Rowan Tommins [IMSoP]
imsop.php@rwec.co.uk wrote:
We are not lying to them right now. They can set longer passwords.
No, they can't. They can input longer passwords, but that input is not stored.
If I encountered a database that truncates names to 10 bytes, I would describe it as not correctly handling longer names. Telling users that they can set longer names, but never actually storing them, would be lying.
Passwords are not stored either. Only the hash is stored. The hash can
be a result of multiple distinct inputs. There is no guarantee that a
password will always produce a unique hash.
It will still match. A hash of 72-byte long password and a hash of
73-byte long password where the first 72 bytes are the same will
match. https://3v4l.org/o7dJq#v8.5.11Yes, and that is a false positive - the user provides the wrong password, and the system lets them in.
It's still a valid password. As I said, conflicts occur, so you don't
have to provide the exact same password to be let in. Just one that
produces the same hash.
Because the password is just as strong as it would be if we didn't
ignore the unnecessary bytes. It would be corrupted if the password
got weaker or changed in a way that it could no longer be verified
against its hash.Of course it gets weaker - it's shorter!
Longer passwords are not automatically stronger. In fact, past a
certain length, they are either all the same strength or even weaker
because an attacker can guesstimate the used characters and structure,
e.g. Diceware
Imagine if it truncated at 7 bytes; would you still call that "correct handling", as long as it was documented behaviour?
Obviously, yes. I don't understand your point.
Then we are stuck. I can't imagine how "most of your password is silently thrown away" can be considered "correctly handled". It can be a known limitation, but from a user's point of view, it's very clearly not doing what they asked, which was to set a longer password.
From a user's POV, it doesn't, and I agree with that. But that's not
very important for what we are discussing here. If it works as
designed, then it works correctly.
Hi,
Yes, and that is a false positive - the user provides the wrong
password, and the system lets them in.It's still a valid password. As I said, conflicts occur, so you don't
have to provide the exact same password to be let in. Just one that
produces the same hash.
You can't base your argument on the premise that it's all fine because the
algorithm itself is strong and at the same time claim that conflicts occur.
Collision resistance underpins all security claims about hashing algorithms.
Imagine if it truncated at 7 bytes; would you still call that
"correct handling", as long as it was documented behaviour?Obviously, yes. I don't understand your point.
Then we are stuck. I can't imagine how "most of your password is
silently thrown away" can be considered "correctly handled". It can be a
known limitation, but from a user's point of view, it's very clearly not
doing what they asked, which was to set a longer password.From a user's POV, it doesn't, and I agree with that. But that's not
very important for what we are discussing here. If it works as
designed, then it works correctly.
... and who's to say that the design is correct to begin with?
Why are you assuming that a design from 1999 has seriously considered the
problem at hand?
Will a 32-bit UNIX timestamp be correct in 2038?
It is very hard to respect your POV when it's based on circular logic.
Cheers,
Andrey.
-----Original Message-----
It will still match. A hash of 72-byte long password and a hash of
73-byte long password where the first 72 bytes are the same will match.
https://urldefense.proofpoint.com/v2/url?u=https-3A__3v4l.org_o7dJq-23v
8.5.11&d=DwIFaQ&c=euGZstcaTDllvimEN8b7jXrwqOf-v5A_CdpgnVfiiMM&r=i0NKIgJ
vrKCThBAHo8lLTC9DcIc_YQfpu9rIABLCJDI&m=BfKD0IS6H873YHy61CdrQdPZPt8HtJQQ
ssJtrmymWnZI8VB-PIfGO_Be4ovErFMc&s=ctggKP-MeLZgXCyZvKx30hgf4i3E-uSaRhl9
_u92xHY&e=
Yes, and that is a false positive - the user provides the wrong password, and the system lets them in.
Rowan Tommins
[IMSoP]
=====
For what it's worth, I stumbled across this issue recently in the older part of our PHP codebase. After coming up with a solution, we forced PW resets for the impacted users. And, to Rowan's point, if this had thrown an exception (or even a warning, tbh) during any PHP version upgrade we would've caught it many years ago.
Looking through the lens of software security in 2026, I cannot think of any situation where this function should silently accept more bytes than it can hash.
Thank you all for your work on the PHP project.
-Jeff (non-voter, trying to influence the vote)
Hi
The algorithm correctly
handles a password whether it's 8 bytes long or 80 bytes long. When
the password is too long, it just ignores the extraneous inputI find this statement completely baffling. It silently throws away
input; I simply do not accept that as "correctly handling" it.Imagine if it truncated at 7 bytes; would you still call that "correct
handling", as long as it was documented behaviour?
I would. This is in fact almost exactly the behavior of the classic
CRYPT_STD_DES algorithm, except the limit is 8 bytes (with the high
bit ignored) with a 64 bit internal state: Even if the additional
characters would not be ignored, the 64 bit state means that there are
equivalent inputs of 8 bytes or fewer for inputs of 9 bytes or more.
It is no longer secure today due to the 56-bit key size, but given the
computing limitations back then, I would consider it reasonable. Of
course users will not be entering raw bytes, but characters. Still 8
randomly selected alphanumeric bytes would be around 48 bits of entropy.
the security of the algorithm is the same.
The algorithm treats 255 different 73-byte inputs as identical. A
different algorithm would distinguish them. Are you really saying those
are equally secure algorithms?
An ASCII diceware password is probably the least information dense
method of choosing a secure password and as per the math I provided in
https://news-web.php.net/php.internals/132694, you can fit 100 bits of
“Diceware entropy” into the 72 byte limit. While not “equally secure”
under a literal reading you are well past the point of diminishing
returns: Trying to guess 100 bits of entropy is impossible, trying to
guess 101 bits is just more impossible. The “stretching” provided by
BCrypt’s cost factor effectively adds another $cost number of bits of
effective security, since increasing the cost by 1 doubles the execution
time.
Constructing one of the 255 colliding 73-byte inputs requires you to
know the 72-byte prefix. In practice knowing the prefix means that you
know the password, so the extra bytes are irrelevant for security.
Furthermore similarly to the classic DES crypt, BCrypt also has an
output size of 184 bits. Once the input exceeds that, there's a
different input that results in the same output. This is a fundamental
information theoretic limit, not an arbitrarily selected one. For a
completely random 40 byte input you are already well beyond the output
size, so some 40 byte inputs necessarily collide, but this doesn't mean
it's feasible to find those.
Forcing the user to select a different password by emitting an error
message if they input a needlessly long one (“more is better”) is not
improving the security, while decreasing user experience.
Best regards
Tim Düsterhus
I would. This is in fact almost exactly the behavior of the classic
CRYPT_STD_DESalgorithm, except the limit is 8 bytes (with the high bit ignored) with a 64 bit internal state: Even if the additional characters would not be ignored, the 64 bit state means that there are equivalent inputs of 8 bytes or fewer for inputs of 9 bytes or more.
Let's follow this example a bit:
If a system used CRYPT_STD_DES, and limited user input to 8 bytes, you might have colliding hashes for "password" and "@7&jp9Q -".
If the system did not limit user entry, it would have colliding hashes for "password1234" and "password !!! password". Note that both would pass a check for "include at least one digit or punctuation mark", but both would actually be breached by someone trying the input "password" which has neither.
Now, truncating at 72 bytes is obviously very different from truncating at 8, but it's possible to imagine scenarios where it could lead to a meaningful loss of entropy. A quick search found this alphabet: https://en.wikipedia.org/wiki/Osage_script Stored high enough in Unicode that it requires 4 bytes of UTF-8 per character, and requires combining diacritics for some sounds. A diceware password of words in that script might truncate at 3 or 4 words.
More likely, as already discussed, a developer misuses the API and prefixes a password, cutting the effective length limit.
It seems to me that refusing those inputs and alerting the developer to the limitation leaves the overall system more secure.
Hi Kamil, Tim,
Replying to two messages in one here to cut down on traffic...
From a user's POV, it doesn't, and I agree with that. But that's not
very important for what we are discussing here. If it works as
designed, then it works correctly.
I think this is the crux of our disagreement: I don't understand why the user's POV is "not very important".
Rowan Tommins
[IMSoP]
Hi
Let's follow this example a bit:
If a system used CRYPT_STD_DES, and limited user input to 8 bytes, you
might have colliding hashes for "password" and "@7&jp9Q -".If the system did not limit user entry, it would have colliding
hashes for "password1234" and "password !!! password". Note that both
would pass a check for "include at least one digit or punctuation
mark", but both would actually be breached by someone trying the input
"password" which has neither.
Yes, but looping back to the initial example you provided: Someone using
the strong randomly generated password "@7&jp9Q -" might still be
breached by someone trying "password", a property of the small state
space and why DES crypt is no longer secure.
Now, truncating at 72 bytes is obviously very different from truncating
at 8, but it's possible to imagine scenarios where it could lead to a
meaningful loss of entropy.
Indeed for BCrypt the situation is quite different: The 72-byte prefix
is only guessable if it is predictable on its own (e.g. someone
selecting their favorite lyrics). In that case the password is weak with
or without the suffix, since you can just plug in the prefix into a
search engine and look up how the song continues. The RFC will do
nothing for them: They are just going to pick the same song, but cut the
lyrics after one verse rather than two.
A quick search found this alphabet:
https://en.wikipedia.org/wiki/Osage_script Stored high enough in
Unicode that it requires 4 bytes of UTF-8 per character, and requires
combining diacritics for some sounds. A diceware password of words in
that script might truncate at 3 or 4 words.
The same applies to Osage: The user is limited to the same 3 to 4 words
and will go with those rather than figuring out something more secure.
See also my reply to Andrey.
More likely, as already discussed, a developer misuses the API and
prefixes a password, cutting the effective length limit.It seems to me that refusing those inputs and alerting the developer to
the limitation leaves the overall system more secure.
My expectation is that developers who are “alerted” to the limitation
will just go with whatever solution makes the error message go away the
quickest instead of trying to understand the impact, leaving the overall
system in a less secure state. Incorrect prehashing would be a good
example here: In an attempt to “fix” BCrypt’s length limitation, folks
have passed the raw output of a hash function to BCrypt, resulting in
NUL-termination becoming relevant and thus reducing security more than
the 72-byte limit ever could. Now the NUL-termination issue is caught by
PHP nowadays, but I'm positive that folks will figure out equally
fragile workarounds. The obvious one being substr($password, 0, 72)
which mimics the status quo, but would carry forward the issue once
PASSWORD_DEFAULT changes from BCrypt to something else, whatever it
may be.
Best regards
Tim Düsterhus
Hi Tim,
The same applies to Osage: The user is limited to the same 3 to 4 words and will go with those rather than figuring out something more secure.
Would they? If I'm asked for a 6-character password, I don't go to a diceware list and pick one word.
Worst case, they do just use those 3 words, and they're no worse off than before. Best case, they pick a different type of password and gain security. I don't see a scenario where they end up with something worse.
substr($password, 0, 72)which mimics the status quo, but would carry forward the issue oncePASSWORD_DEFAULTchanges from BCrypt to something else, whatever it may be.
As a developer, I have three options of how to deal with the bcrypt limitation:
- reject longer passwords
- truncate longer passwords
- use a different algorithm
At the moment, if I don't choose explicitly, the implementation chooses option 2 for me. The RFC changes that to option 1, but I still have the exact same choices.
Doing the truncation explicitly would mean I can correctly perform additional validation, such as looking up on haveibeenpwned, using the truncated string, not the full input.
I would even argue that keeping the truncation after switching algorithm might be a valid choice, at least in a rehashing flow; because otherwise I am "rehashing" an input which might be varying every time the user types it, rather than the part of the string actually being verified.
On the other hand, rejecting instead of truncating would mean I can correctly handle things like "must not match previously used password". And the user has the chance to change their behaviour based on the limitation, rather than being lied to.
Neither solution is great, and yes, plenty of developers won't think it through, and plenty of users will set terrible passwords either way; but by making the limitation more noticeable, we at least give people a chance to do something about it.
Rowan Tommins
[IMSoP]
-----Original Message-----
From: Tim Düsterhus tim@bastelstu.be
Sent: Wednesday, September 30, 2026 6:08 PM
To: Rowan Tommins [IMSoP] imsop.php@rwec.co.uk
Cc: internals@lists.php.net
Subject: Re: [PHP-DEV] [RFC] Throw error for passwords longer than 72 bytes in password_hash() with bcrypt
It seems to me that refusing those inputs and alerting the developer
to the limitation leaves the overall system more secure.
My expectation is that developers who are “alerted” to the limitation will just go with whatever solution makes the error message go away the quickest instead of trying to understand the impact
=====
If that's the case, then the "throw" proposal is an obvious win. Developers who would do as you feel have an easy remedy. Those who are competent are warned of a significant security issue, that they can properly analyze and resolve.
-Jeff
I appreciate the debate on this topic, and I am in support of the RFC.
It sounds to me like the API has been lost in the debate though.
We all agree Bcrypt is working as intended.
What we are seeing in password_hash() is an unhelpful API. The function
accepts an input of arbitrary length, but the manual contradicts the
arbitrary length usage with a not-entirely-accurate "Caution" block
about the length of the input. It specifically says PASSWORD_BCRYPT,
and misses the identical concerns with PASSWORD_DEFAULT or null.
What the manual doesn't illustrate is a real case where
password_verify() doesn't verify the password.
$hash = password_hash('one two three four five six seven eight nine ten
eleven twelve thirteen epic secure password', PASSWORD_DEFAULT);
$result = password_verify('one two three four five six seven eight nine
ten eleven twelve thirteen ', $hash);
var_dump($result);
A developer might ask why the application is doing this, but the RFC
asks why is PHP doing this? Is the function named password_verify going
to verify the password or not?
Robert Chapin