Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

expose Argon2 options (as we did for bcrypt) #19023

Merged
merged 4 commits into from Jan 23, 2020
Merged

Conversation

blizzz
Copy link
Member

@blizzz blizzz commented Jan 20, 2020

helps with #17131 #17241 at least

At the moment we already expose the settings for bcrypt ('hashingCost'), but since PHP 7.2 we use Argon2 instead. In the issue linked at the beginning there are complaints about high CPU usage. I saw similar when profiling a different case.

The PHP default config (on my system) is to take up to 64 MB RAM on a single thread for up to 4 seconds. You feel it. Now, for more powerful systems it is reasonable to allow using more threads, while for weaker machines less RAM (or a shorter time) might make sense.

Default PHP config is followed if no override is given in config.php.

For >=18 we should removed the hashingCost config. I intend a follow up config, so this one can be backported easily.

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
Copy link
Contributor

@kesselb kesselb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for picking this up 👍

lib/private/Security/Hasher.php Outdated Show resolved Hide resolved
lib/private/Security/Hasher.php Outdated Show resolved Hide resolved
lib/private/Security/Hasher.php Outdated Show resolved Hide resolved
Copy link
Member

@ChristophWurst ChristophWurst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 with @kesselb's suggestions :)

@rullzer
Copy link
Member

rullzer commented Jan 20, 2020

Yep lets use the suggestions form @kesselb and then I'm fine with it!

Co-Authored-By: kesselb <mail@danielkesselberg.de>

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
@blizzz blizzz force-pushed the enh+fix/17131/hasher-config branch from 3e52d1c to 56c3ba6 Compare January 21, 2020 08:05
@blizzz blizzz added 4. to release Ready to be released and/or waiting for tests to finish and removed 3. to review Waiting for reviews labels Jan 21, 2020
@blizzz
Copy link
Member Author

blizzz commented Jan 21, 2020

/backport to stable18

@blizzz
Copy link
Member Author

blizzz commented Jan 21, 2020

/backport to stable17

@blizzz
Copy link
Member Author

blizzz commented Jan 21, 2020

/backport to stable16

@rullzer
Copy link
Member

rullzer commented Jan 21, 2020

You can't backport that far. Or then you need some hardening. As this is only available starting at php 7.2

@blizzz
Copy link
Member Author

blizzz commented Jan 21, 2020

@rullzer 16 supports 7.2, so that should work. Provided, that unused parameters are being ignored, I am going to double check that.

@blizzz blizzz added 2. developing Work in progress and removed 4. to release Ready to be released and/or waiting for tests to finish labels Jan 21, 2020
@blizzz
Copy link
Member Author

blizzz commented Jan 21, 2020

@rullzer 16 supports 7.2, so that should work. Provided, that unused parameters are being ignored, I am going to double check that.

Checked again, no conflicts.

Because hash_password fails when minimum values are undershot, I'll add some checks.

@blizzz blizzz added 3. to review Waiting for reviews and removed 2. developing Work in progress labels Jan 21, 2020
@blizzz
Copy link
Member Author

blizzz commented Jan 21, 2020

… this should also fix CI now

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
@blizzz blizzz force-pushed the enh+fix/17131/hasher-config branch from b35b45d to f92ba2c Compare January 22, 2020 15:05
@kesselb
Copy link
Contributor

kesselb commented Jan 22, 2020

Bonus points if you add the minimum value to config.sample.php ;)

@blizzz
Copy link
Member Author

blizzz commented Jan 22, 2020

Bonus points if you add the minimum value to config.sample.php ;)

but but but but it is document in co… … oh. I see. Okay, for the Karma!

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
@blizzz blizzz added the pending documentation This pull request needs an associated documentation update label Jan 22, 2020
@blizzz
Copy link
Member Author

blizzz commented Jan 22, 2020

Added the label for proper documentation as suggested in #18677 (comment) (even though this is rather for admins)

@blizzz blizzz merged commit 2f27f12 into master Jan 23, 2020
@blizzz blizzz deleted the enh+fix/17131/hasher-config branch January 23, 2020 10:47
@backportbot-nextcloud
Copy link

backport to stable18 in #19094

@backportbot-nextcloud
Copy link

backport to stable17 in #19095

@backportbot-nextcloud
Copy link

backport to stable16 in #19096

@kesselb
Copy link
Contributor

kesselb commented Jan 23, 2020

cc @nachoparker @MichaIng probably something for NextcloudPi and DietPi

@MichaIng
Copy link
Member

MichaIng commented Jan 23, 2020

@kesselb
Many thanks for the info, indeed sounds like something we could apply reasonable defaults based on hardware.

Just to give some impressions from RPi2:

2020-01-23 13:06:33 root@micha:/var/www# cat test.php
<?php
echo 'Argon2i hash: ' . password_hash('nextcloud', PASSWORD_ARGON2I, $_GET);
?>
2020-01-23 13:06:48 root@micha:/var/www# time curl -k https://localhost/test.php
Argon2i hash: $argon2i$v=19$m=65536,t=4,p=1$NnZwbFRQdEZSd2NyODV3VQ$wXU+k4uRbVwUDjQjynSfXfOikJC/Vw+skHAudig9lKQ
real    0m5.832s
user    0m0.229s
sys     0m0.046s
  • Defaults on Raspbian hence 64 MiB, 4 seconds iterations and 1 thread.
2020-01-23 13:13:53 root@micha:/var/www# time curl -k "https://localhost/test.php?memory_cost=8"
Argon2i hash: $argon2i$v=19$m=8,t=4,p=1$UHZTa3B4MVZwZ0E2QUhXTw$3+qh461BTCIolf7RL59vjvczoq43YeLDel820KrBkk0
real    0m0.465s
user    0m0.215s
sys     0m0.057s
2020-01-23 13:14:36 root@micha:/var/www# time curl -k "https://localhost/test.php?time_cost=1"
Argon2i hash: $argon2i$v=19$m=65536,t=1,p=1$ZUw5WlNwVmJMVTVkZC9wSw$XvfkgN1qPRK3VQOfTOn6OgE3vmcPk8V5AamFcAZEM9c
real    0m1.902s
user    0m0.199s
sys     0m0.071s
2020-01-23 13:14:43 root@micha:/var/www# time curl -k "https://localhost/test.php?threads=4"
Argon2i hash: $argon2i$v=19$m=65536,t=4,p=4$MEZvLlRuSXRJcDhZdE5aTg$V4x5jc8iCn9yFmS6qZ3Ncv8wtRNQ3stMBw54EUQGVQ0
real    0m1.948s
user    0m0.234s
sys     0m0.039s
  • Not sure if I actually understand things. I mean the hash will be simply as strong as those threads are able to do with given memory in given time? But different hardware is able to do different times with same amount of threads, memory and time 🤔. EDIT: time_cost does not define the amount of time but the amount of times the hash function iterates.

Btw important to know:

2020-01-23 13:19:16 root@micha:/var/www# curl -k "https://localhost/test.php?memory_cost=8&threads=4"; journalctl -u apache2 | tail -1
Argon2i hash:
Jan 23 13:20:03 micha.gnoedi.org apache2[25784]: [php7:warn] [pid 25784] [client 127.0.0.1:39524] PHP Warning:  password_hash(): Memory cost is too small in /var/www/test.php on line 2
2020-01-23 13:20:03 root@micha:/var/www# curl -k "https://localhost/test.php?memory_cost=16&threads=4"; journalctl -u apache2 | tail -1
Argon2i hash:
Jan 23 13:20:16 micha.gnoedi.org apache2[25577]: [php7:warn] [pid 25577] [client 127.0.0.1:39526] PHP Warning:  password_hash(): Memory cost is too small in /var/www/test.php on line 2
2020-01-23 13:20:16 root@micha:/var/www# curl -k "https://localhost/test.php?memory_cost=32&threads=4"; journalctl -u apache2 | tail -1
Argon2i hash: $argon2i$v=19$m=32,t=4,p=4$aVVtd2RxZEtSMzBNd0hDaQ$Liqk/dseO+NzASJdF8urHZnYbOfI836GlO9cocxUdQ0
Jan 23 13:20:16 micha.gnoedi.org apache2[25577]: [php7:warn] [pid 25577] [client 127.0.0.1:39526] PHP Warning:  password_hash(): Memory cost is too small in /var/www/test.php on line 2
  • The minimum memory is 8 KiB per-thread! This must be accordingly checked correctly, else we'll get empty hashes!

if (\defined('PASSWORD_ARGON2I')) {
// password_hash fails, when the minimum values are undershot.
// In this case, ignore and revert to default
if ($this->config->getSystemValueInt('hashingMemoryCost', PASSWORD_ARGON2_DEFAULT_MEMORY_COST) >= 8) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be >= 8 * $hashingThreads: #19023 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @blizzz

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MichaIng mind to send a patch?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kesselb
PR up, but requires another change where I am not sure how to deal with best. Please have a look: #20710

@7usr7local
Copy link

Thanks for implementing this, it helped me a lot - after all, after reducing the hashing costs by this parameters for argon2, my server is responding to requests again! :-)
I am a bit confused though. My passwords are in LDAP, my files are not encrypted - at least not nextcloud-wise (It's called "hashing", not "encryption" anyway).
So where does hashing take place so that it has such a big impact on overall server performance?
Or to put it more practial: What are the security implications of reducing the hashing costs? And may I ask to put the answer into the documentation here?

@MichaIng
Copy link
Member

Yes good question. Argon2 is a password hash method, so used to hash and compare sensitive strings, not to encrypt data with the purpose of later decrypt. I was checking the code to see where this hash function is used (surely missed many):

Generally of course where an authentication with password against Nextcloud is done or a new password is set, so there is only a hashed version stored and the input hashed and compared. File integrity check is obvious.

So where does hashing take place so that it has such a big impact on overall server performance?

Did you recognise anything else then when making new connections with clients or web login? The above cases I found should e.g. not affect browsing the web UI or performance of file transfer after connection has been established, but probably I have overseen an important use. EDIT: Having a closer look, even in many other cases different hash() functions are used.

Or to put it more practial: What are the security implications of reducing the hashing costs?

That is also what I am most interested in. Some readings: https://en.wikipedia.org/wiki/Argon2
Basically based on max memory and threads, a vector of 1KiB blocks is created in memory to process hashing. I lack details but the strength of the hash per iteration reasonably is related to the size of the array. And now where I initially (see above) misunderstood the other parameter "time cost" is not about the amount of seconds or "time" the computation should take, but the number of "times"/iterations of the hash function. Now everything makes totally sense. Funny that this is mixed up in several docs: https://www.php.net/manual/en/function.password-hash.php

Now I am confused, and it seems I am not the only one, sadly... On my RPi, time cost and time in seconds matched with max execution time when considering the additional time for connection, script processing and such. Another test on much stronger system should give quick verification which is true.

Ah found the Nextcloud docs also saying that it is about the amount of seconds. What I don't understand, if this is true, as asked above already, is how one knows the amount of iterations that need to be done for hashing, when you want to compare the result to a stored hash, as there is only one t value stored in the hash result. Since the time in seconds it took to compute the hash, may highly vary, it cannot play any role for future hashing, hence it does not make any sense to print or store it, while the amount of iterations must be the same to get the same result and allow comparison?

Regardless, the articles link several possible attacks and how Argon2i vs Argon2d vs Argon2id make it harder, how much iterations are considered as safe etc. But while I lack insights, it seems like an attacker needs direct access to the hardware or needs to execute the hash function repeatedly on your system to apply any of those attacks. In regular cases the default values obviously can be reduced significantly (above link: memory_cost = 1024 KiB; time_cost = 2; threads = 2) while still making it practically impossible (in reasonable amount of time) to crack the hash without above mentioned system access.

So now I want to know what which half of docs is wrong...

@MichaIng
Copy link
Member

MichaIng commented Jul 17, 2020

Okay, the time_cost variable definitely has nothing to do with the actual time used or as limit for the hashing, but it then reasonably defines the number of times the hash function loops:

# time php -r 'echo password_hash("nextcloud",PASSWORD_ARGON2ID,array("time_cost" => 1));'
$argon2id$v=19$m=65536,t=1,p=1$Z1FpZjBFWVdGYkxPMUVwLg$2oKhi7+S6Bxje+ww9aFbRY+BmgOVhhwKMsBxv490AZc
real    0m0.209s
user    0m0.196s
sys     0m0.012s
# time php -r 'echo password_hash("nextcloud",PASSWORD_ARGON2ID,array("time_cost" => 4));'
$argon2id$v=19$m=65536,t=4,p=1$LnNqTWZOTlNPTlNTZGdTTg$jDQnBQlfsQdbnAc+F8wqQvw5vdAENpzZBOW7tEr5hlw
real    0m0.710s
user    0m0.686s
sys     0m0.024s
# time php -r 'echo password_hash("nextcloud",PASSWORD_ARGON2ID,array("time_cost" => 16));'
$argon2id$v=19$m=65536,t=16,p=1$cy9MN3JkbS40NTVERDJLVw$4EaQApnVEmMyS7qrOGjrZdgIFvetYyZnU6Tp7tkklHU
real    0m2.662s
user    0m2.637s
sys     0m0.024s
# time php -r 'echo password_hash("nextcloud",PASSWORD_ARGON2ID,array("time_cost" => 16,"threads" => 4));'
$argon2id$v=19$m=65536,t=16,p=4$WG94VWVWNE8uSHZTeW1kUw$htn9rtNe7SYmxLAwQr51BN927FHydnqfNT0cpLwQb9Y
real    0m1.032s
user    0m3.709s
sys     0m0.073s
## Just to show that more threads then CPU/threads available doesn't make sense 😉
# time php -r 'echo password_hash("nextcloud",PASSWORD_ARGON2ID,array("time_cost" => 16,"threads" => 8));'
$argon2id$v=19$m=65536,t=16,p=8$eHpBbG10THRrMDlNVk9WcA$aCXCg5Oney07jETj7f4cGR+x0d4eI0UnoRV3PqDTq0k
real    0m1.038s
user    0m3.719s
sys     0m0.109s

This totally makes sense as, when one wants to verify the hash, this is what one must know, and not how much time the particular machine required or had as limit to create the hash in its particular load state...

@blizzz I think something got mixed up here, I guess also due to the bid unclear (likely as well due to misunderstanding by author) PHP documentation, but this should be definitely fixed in code comment and documentation. But probably I am wrong and you can review and verify first as this is all based on logic thoughts, the fact that the same input, especially time_cost, leads to different actual process duration on different machines + does not correlate with actual processing duration and the clearer PHP Argon2 wiki.

And indeed it would be nice to know in which cases this is used, so one has a good basis to find reasonable numbers for ones particular environment and use case. I could also add the small script to the docs so one can test which values lead to which times. Also recommendations could be added, like:

  • hashingThreads => usually should match nproc output
  • hashingTimeCost => should usually left default (4 times) which is assumed to be save from what I read
  • hashingMemoryCost => and this is where usually one would want to play around with, since it directly affects the memory consumption for the hashing (matches memory_cost ~1:1) and as well is ~proportional to the required hashing duration.
    In special environments (plenty of memory but weak CPU or strong CPU but very limited memory) one might want to balance time_cost and memory_cost carefully, since the security of half time_cost + doubled memory_cost or the other way round does not equal. However this would require more reading and then also depends on Argon2i vs Argon2id and the use case.

There are many more cases that show that either me and PHP wiki are wrong or the whole rest of the world 😄:

All talk about execution time or time in seconds...

Here someone got it right, calling it the "Number of iterations t" that of course affect processing time: https://crypto.stackexchange.com/a/37140

MichaIng added a commit to MichaIng/DietPi that referenced this pull request Jul 17, 2020
+ DietPi-Software | Nextcloud: Add Argon2 hashing tweaks. Use all available CPU threads for now, which radically reduces hashing time one multi-core SBCs, wait with memory cost and time cost until getting some clarification, especially since those are security relevant and defaults do not break anything: nextcloud/server#19023 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3. to review Waiting for reviews bug enhancement pending documentation This pull request needs an associated documentation update
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants