Problem/Motivation
Argon2 password hashing used to depend on PHP compiled against libsodium and/or libargon2. For that reason, that hashing algorithm was not universally available on every hosting provider / linux distribution. In PHP 8.4 argon2 was made available via openssl. Drupal 12 requires PHP 8.5. It is safe to assume that argon2 is supported wherever Drupal 12 is.
For the rare case where argon2 is not supported, or if the algorithm kernel parameter contains invalid data, the password hashing should fall back to the PHP default (which is bcrypt at the time of this writing).
Steps to reproduce
Proposed resolution
- Modify the constructor of
PhpPasswordto make the$algorithmparameter nullable. - Check whether the algorithm passed into the constructor is available, fall back it to
NULL(default algorithm) if not - Add parameter references to the
passwordservice definition and add two new kernel parameters:
password.algorithm: argon2id password.options: [] - Add appropriate requirement checks to warn if argon2id is not available and the password hashing is falling back to PHP default.
Remaining tasks
User interface changes
Introduced terminology
API changes
Data model changes
Release notes snippet
The default password hashing algorithm was switched to argon2id in Drupal 12. For the rare case where this is not desirable - or argon2 is not available - it can be switched to the PHP default algorithm (bcrypt) using kernel parameters.
| Comment | File | Size | Author |
|---|
Issue fork drupal-3530186
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
znerol commentedComment #4
znerol commentedComment #5
znerol commentedComment #6
znerol commentedPostponing on #3530235: Add libargon2 and libsodium to PHP image
Comment #7
andypostCI images has both now
Comment #8
andypostconstructor only changes looks ok but it require container rebuild so needs update hook to request it, otherwise looks ready
Comment #9
znerol commentedThanks! Added an update hook.
Comment #10
andypostThank you! CR is ready and the code too
Comment #11
longwaveLooks good to me, added a question about the update hook though.
Comment #12
znerol commentedComment #13
benjifisherThanks for working on this issue. As I said on #3536662: Properly handle that PHP bcrypt passwords are truncated to 72 bytes,
bcrypt.hook_requirements. We could do that as part of this issue or on #3536662.@longwave mentioned #2951046 to me. I am adding that as a related issue. Once that issue gets in, we can use something like
!php/const PASSWORD_ARGON2IDinservices.yml.Comment #14
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #15
znerol commentedComment #16
andypostI think requirements exposition of available hashes should be done in #3536662: Properly handle that PHP bcrypt passwords are truncated to 72 bytes
This issue exactly about to add support of argon and make it configurable, also soduim open doors to fast encrypted storage
Comment #17
larowlanLeft a minor question on the MR, fine to self RTBC
Comment #18
znerol commentedComment #24
larowlan@catch mentioned on slack
Sorry for the runaround @znerol - can we remove that?
We discussed this on the security team triage call in relation to #3536662: Properly handle that PHP bcrypt passwords are truncated to 72 bytes - in particular that issue's reintroduction of the legacy phpass hashing for users with passwords longer than 72 bytes where the bcypt algorithm is in use.
We decided we would instead prefer to move forward with this issue rather than what is effectively backwards with the reuse of phpass.
We decided we would prefer to have the hook_requirements from that issue added here that:
I've manually tested that changing the hashing algorithm with existing users successfully rehashes their password in the new algorithm when they next login.
I will mark the other issue as postponed on this one in the meantime.
We also decided to re-purpose the php_password contrib module as a BC layer as the changes in this MR can't be backported.
So this issue here will fix 11.3/10.6 and the contrib module will decorate the core service to bring this functionality to stable branches. The contrib module will have a forward compatibility layer that will be a no-op if it detects the container param added here - which would indicate the module is no longer needed as the site has updated to a version with this feature.
With this issue and the contrib module ready, we will likely issue a PSA
Crediting those involved in the discussion.
Comment #25
andypostRemoved post update hook
Comment #26
catchI think this needs:
from #24.
Comment #27
larowlanforwards compatibility layer is ready for review here https://git.drupalcode.org/project/php_password/-/merge_requests/6/diffs
Comment #28
znerol commentedAdded the requirement checks.
Should we add a config and a proper UI to make these requirement checks more actionable? Maybe an additional Password fieldset in
admin/config/people/accounts?Comment #29
znerol commentedComment #30
andypostI think transition to new hash should be done in follow-up, moreover old passwords makes no sense when Argon2 available
Comment #31
benjifisher@andypost:
What transition do you mean?
Passwords should be rehashed automatically when users log in with their passwords. The Q&A for the Password Compatiblity module apply to this situation, too.
From #24 here:
@znerol:
I have been thinking that this is something that would be managed through
services.yml, not the UI. In fact, I like the idea of relying on something in the filesystem. Otherwise, XSS can be exploited to change the password hashing algorithm.If you still want to provide a configuration form, then let's have a followup issue for further discussion.
Edit: I initially wrote
settings.yml. I meantservices.yml.Comment #32
znerol commentedBy the time this ships, every single site which upgrades will generate a requirements warning. The requirements warning points to a CR which contains example code for a service provider class. The example code for the service provider class is there because I think we shouldn't instruct people to manually add a container parameter for this. The reason I think so is that it is safer to use the
\PASSWORD_*constants instead of let people attempt to hard code the exact password algorithm into a yml file by hand.Regarding the UI: This could be just a
password_argon2module with its own requirements check and a service provider which adds thepassword.algoritm: argon2idcontainer parameter.Comment #33
benjifisherHow is it easier to use the
\PASSWORD_*constants? Usingdrush php:I think we should let people set a hashing algorithm in
services.yml.Have we considered what to do if the service parameter is invalid? Do we fall back to
PASSWORD_DEFAULT? Is there a requirements check (for the status page)?Comment #34
znerol commentedIn the MR. There is a requirements check for unknown / misspelled password algorithms.
When an algorithm is configured which isn't available on the system, attempting to create new password hash will result in a fatal:
Fatal error: Uncaught ValueError: password_hash(): Argument #2 ($algo) must be a valid password hashing algorithm in Command line code on line 1Verifying existing hashes still works (assuming the hashing algorithm is available on the system).
Comment #35
larowlanI updated the change record to add an example of setting the parameters via a services.yml file
Fixed a couple of typos in the MR.
Going to try to simplify the hook_requirements a bit
Comment #36
larowlanMoved the requirements checking logic to a new private method which enabled me to reduce the cyclic complexity/nesting.
As this was just moving things around I think I'm still ok to mark this as RTBC. So doing so.
Comment #37
larowlanComment #38
larowlanhttps://www.drupal.org/project/php_password/releases/3.0.0 has been released with the forward compat layer
Comment #39
andypost+1 RTBC as no code change just move and s/know/known text change
Comment #40
catchWith the change record, is
password.optionsreally required? It looks like it defaults to empty array anyway and could be omitted.Also should some documentation be going into default.services.yml? The issue summary mentions this but there's no changes in the MR.
Comment #41
larowlanUpdated the CR to make it clear that password.options is optional.
Added docs to core.services.yml and default.services.yml
Comment #42
quietone commentedThe link in the message is to a change record. Should that be to a documentation page instead?
Comment #43
catchI think we should consider removing the hook_requirements() from here altogether, and sorting that out in #3536662: Properly handle that PHP bcrypt passwords are truncated to 72 bytes where we have multiple options that might not require it (at least on all sites, we still might end up with something similar on some sites).
Comment #44
quietone commentedI hadn't read the other issue, #3536662: Properly handle that PHP bcrypt passwords are truncated to 72 bytes, until now. I like the suggestion in #43.
Comment #45
znerol commented@catch that would basically reverse the outcome from the security team triage call (#24). I'd rather try to go into a direction which doesn't send us running around in circles.
Comment #47
znerol commentedAdded a draft MR with an Password Argon2 module. This is implementing the idea from #32.
Comment #48
andypostsadly it will go to 12 now
Comment #50
smustgrave commentedCould 1 MR be closed?
Comment #51
znerol commentedLet me check.
Comment #52
znerol commentedComment #54
znerol commentedComment #55
znerol commentedComment #56
benjifisherIn Comment #31, I wrote,
I have not seen any reply to that comment. The current approach does not provide a configuration form, but it does provide a module that sets the hashing algorithm to
argon2.My point is that something in the filesystem is fundamentally more secure than something in the database. Enabling the module is simple, and it reliably sets the algorithm name to a valid value, but it is not as secure as changing
services.yml. If an attacker can disable the module, then hashing goes back tobcrypt, and no one will notice until they look at the status report.There are other, less important, reasons I do not like the module:
services.ymlor/admin/modules.announcements_feedmodule for that reason.)Comment #57
benjifisherSome of the new interface text:
I would like to get rid of the word "new" in this text. Unless I am confused, existing passwords will be re-hashed with the current algorithm the first time they are used after the algorithm is updated. How about something like this?
Comment #58
znerol commentedRe #56 the reason is UX: Forcing people to edit
services.ymlto get rid of a requirements warning is not an option.Also bcrypt is still the default password hashing algorithm in PHP and it is safe to use. Even though there are edge cases like the ones documented in #3536662: Properly handle that PHP bcrypt passwords are truncated to 72 bytes. An attacker wouldn't gain anything by removing the argon2 module unless they know that edge case passwords are used on the site.
The goal of this issue is to encourage the switch with an easy way to opt-in.
If people prefer to change the goal, then I'd propose to enforce the switch via
core.services.ymlwith a way to opt-out viaservices.yml. Drupal 12 requires PHP 8.5 And since PHP 8.4 argon2 is available from openssl. This means that PHP doesn't need to be compiled against libsodium or libargon2 in order to support it. The risk that a hosting provider lacks argon2 with PHP 8.5 is nearly zero.Comment #59
longwaveAs I commented in Slack: I wonder if this is something that should be controlled by settings.php? That feels like an easier place to document opt-in functionality. If we are confident argon2 is widely available in PHP 8.5 we could default to that in new installs, and fall back to bcrypt only on existing sites where the setting is not set at all. Sites can upgrade by copying the default stanza from default.settings.php to settings.php, and we can mention this in the D12 release notes.
Comment #60
longwaveDiscussed in Slack with @benjifisher and @catch. I am also not convinced that a separate module is the right way to go here, it leaves us with something more complicated to deprecate and remove in future.
A combined suggestion from the Slack discussion:
Assuming that we think argon2 will be generally available in PHP 8.5, this seems reasonable to me. Sites are more secure by default, with backward compatibility. The warning will be shown to site owners who are either unaware or who have explicitly chosen bcrypt.
Benji raised the point that it is possible that users will transfer databases (or more explicitly, password hashes) from an argon2 environment to a non-argon2 environment, and logins will not work. We could technically detect an unsupported algorithm at login time by inspecting the hash, but I'm not sure whether we would want to warn users or not. I assume that most site owners who move databases between environments will also have Drush available as a backup login mechanism, and the status report would warn them that bcrypt is in use (and perhaps we could even extend it to detect unsupported hashes - we had a similar idea to inform site owners that it is safe to remove phpass).
Comment #61
znerol commentedComment #64
andypostChange record said it will fallback to bcrypt if no argon available but constructor set it to null, not clear how it will work and how to test as all my PHP installs has argon2 available
Comment #65
znerol commentedComment #66
znerol commentedRe #64: it is possible to test the fallback by setting
password.algorithmto some gibberish. The MR even has a test for that, see core/tests/Drupal/KernelTests/Core/Password/PhpPasswordUnknownAlgorithmIntegrationTest.php.Comment #67
andypostThank you, then it looks RTBC
Comment #68
longwaveLooking good - thanks for adjusting the MR so quickly, thanks to other reviewers for feedback so far. I've added a couple more comments but this looks pretty close to me.
Comment #69
znerol commentedAdded test coverage for the requirements check.
Comment #70
longwaveThanks - there is a bug in one of the tests, other than that this looks good to me.
Comment #71
znerol commentedComment #72
longwaveI don't think we will accidentally break that test again, this all looks good to me now.
Comment #73
benjifisherI like this version of the MR much better than the last time I looked (Comment #57). Thanks for continuing to work on it, @znerol, @longwave, and others! In particular, there is no longer a module that enables
argon2, which I consider less secure than setting the password algorithm inservices.yml.The change record refers to 11.3 (So does the project page for https://www.drupal.org/project/php_password), so I am adding the issue tag for a CR update. I also think the CR should mention that site owners can use
services.ymlto set a different algorithm (if their version of PHP supports it) or add parameters. I can update the CR, pending an answer to the next question.What is the plan for Drupal 11? I guess the idea is that Drupal 12 requires PHP 8.5, so
argon2will be more widely available, but that does not apply to Drupal 11.4. I think we should make the same changes to 11.4 that we make to 12.0, except thatcore.services.ymlshould havepassword.algorithm: ~(default toPASSWORD_DEFAULT) for BC. That way, any site owners who care can start usingargon2as soon as they update to 11.4. Also, we will get a little closer to the goal of "D12.0 is the same as D11.4, with deprecated code removed and a few other necessary differences".Eventually, maybe in PHP 9,
PASSWORD_DEFAULTwill be something other than2y(bcrypt). When we require such a version of PHP, we should changepassword.algorithm: argon2idtopassword.algorithm: ~incore.services.yml. Should we add a code comment to that effect? Is there a better way to keep track? In the far future,PASSWORD_DEFAULTwill be something better thanargon2id.We should document that
argon2support is required for Drupal 12. (Or maybe just recommended, since site owners can override it inservices.yml.) Is #3534144: [meta] Set Drupal 12 platform and browser requirements by Jan 15 2026 the right place to do that? I think the right documentation page is PHP requirements. Should we also add something to the release notes? The issue summary does not yet have anything under "Release notes snippet".We consider this issue to be a Security improvement, don't we? I am adding the issue tag for that.
I plan to do some more manual testing tomorrow.
Comment #74
znerol commentedI removed the section about the
php_passwordmodule from the CR for now. It can be added again if/when the patch is ported to branches < 12. The CR already shows how to set parameters inservices.yml. I'm not sure what #73 is suggesting in this regard.Regarding changes to Drupal 11. Yes, maybe some of this MR is portable. We could add the kernel parameters and also add a subset of the requirements checks.
Regarding future proofing: The password hashing parameters need to be revisited whenever the bar is raised for minimum PHP version. The evolution of
PASSWORD_DEFAULTfollows a documented policy. Sudden changes are not to be expected:I updated the issue summary with a release notes snippet.
Comment #75
benjifisherI did some manual testing. Before Comment #73, I also reviewed the non-test portion of the CR. +1 for RTBC.
Manual testing
I wanted to make sure that the changes for this issue work with the core
phpassmodule. These are my steps:1. In a Drupal 7 site, create several user accounts with known passwords (
kp).2. Install Drupal 11.x with the Standard profile.
3. Enable the
migrate_drupalmodule and its dependencies (migrateandphpass).4. Migrate the users from the D7 site:
ddev drush migrate:import d7_user --execute-dependencies.5. Log in as
kp-user1.6. Uninstall the
migrate_drupalmodule (only).7. Switch the branch for this issue and
composer install.8. Run database updates.
9. Log in as
kp-user1.10. Log in as
kp-user2.To set up for migration,
settings.phpfor the D11 site defines$databases['migrate']['default'], settinghosttoddev-drupal7-db. Reference: Managing projects > Inter-Project Communication. Confirm the setting withddev drush --database=migrateand then check the hashed passwords on the D7 site:After Step 5,
phpasshas updated the hash forkp-user1on the D11 site:I get the same result after Step 8. After Step 10:
Comment #76
catchI've added a note to the PHP requirements page on https://www.drupal.org/docs/getting-started/system-requirements/php-requ... - would be good to get a second set of eyes on that.
Comment #78
benjifisher@catch:
I looked at the new section https://www.drupal.org/docs/getting-started/system-requirements/php-requ....
I updated the draft change record. In part, I added the following section:
Then I replaced the second sentence you added to the PHP requirements page with
Comment #80
catchThanks for checking the docs @benjifisher.
Committed/pushed to main, thanks!
Comment #83
znerol commentedThanks!
From #73:
I'm not sure about the process here. Should I open a new MR on this issue or create a follow-up for Drupal 11?
Comment #84
catchAdd the release notes snippet to the alpha1 release notes draft and published the CR.
Comment #85
longwave@znerol Let's open a followup for that - the title of this one should stay the same, the change we are backporting is a bit different.
Comment #86
znerol commentedOpened #3581966: [11.x] Introduce kernel parameters for password hashing algorithm and options.
Comment #88
catchBelatedly tagging for a release highlight, doesn't hurt to tell people their sites will get more secure after a major update.