Skip to content

Conversation

GreyXor
Copy link
Contributor

@GreyXor GreyXor commented Aug 17, 2025

Hello 👋,

According to the OWASP Password Storage Cheat Sheet, the recommended modern algorithm for password hashing is Argon2id, with bcrypt only being considered a legacy option.

Use Argon2id with a minimum configuration of 19 MiB of memory, an iteration count of 2, and 1 degree of parallelism, with a 16-byte salt.

Currently, Caddy’s basicauth directive only supports bcrypt. While bcrypt is still reasonably safe with sufficient work factor, Argon2id provides stronger protection against modern hardware attacks (GPU/ASIC/FPGA), thanks to its memory-hard design.

Motivation

  • Aligns Caddy with OWASP’s current best practice.
  • Gives operators stronger password security without requiring external tooling.
  • Argon2id is currently the best practice for password storage in 2025. Its flexibility and security make it preferable over bcrypt, scrypt, or older algorithms.
  • Provides forward compatibility as bcrypt adoption continues to decline in favor of memory-hard algorithms.

Proposal

This PR adds Argon2id support as a handler alongside bcrypt, with recommended defaults:

  • Memory: 46 MiB
  • Iterations: 1
  • Parallelism: 1
  • Salt length: 16 bytes
  • Key Length: 32 bytes

Implementation notes

  • Uses golang.org/x/crypto/argon2.
  • Encodes hashes in PHC string format ($argon2id$v=19$m=…).
  • Defaults follow OWASP recommendations; parameters can be extended for configurability in future if desired.

Backward compatibility

  • Existing bcrypt-based basicauth configs continue to work unchanged.
  • bcrypt remains the default algorithm.
  • Operators can migrate to Argon2id at their own pace.

Related doc/website PR : caddyserver/website#486

@GreyXor
Copy link
Contributor Author

GreyXor commented Aug 17, 2025

Example of hash-password command

caddy hash-password --plaintext "hiccup" --algorithm "argon2id"
$argon2id$v=19$m=47104,t=1,p=1$zJPvVe48N64JUa9MFlVhiw$b5Tznu0PxnA4TciY6qYe2BFPxncF1ePQaeNukHhH1cU

Example of Caddyfile

basic_auth /secret/* argon2id {
    # Username "Bob", password "hiccup"
    Bob $argon2id$v=19$m=47104,t=1,p=1$zJPvVe48N64JUa9MFlVhiw$b5Tznu0PxnA4TciY6qYe2BFPxncF1ePQaeNukHhH1cU
}

In this Caddyfile we authenticate with Bob:hiccup credential

@GreyXor GreyXor marked this pull request as ready for review August 17, 2025 10:14
@francislavoie
Copy link
Member

See this tweet thread (that I happened to be involved in lmao) from people involved in the password hashing competition which chose argon many years ago: https://x.com/TerahashCorp/status/1155129705034653698 in the end argon is not as good as bcrypt for hashing under 1s which is absolutely the target for web scenarios (usually you want 0.3s or less so it doesn't feel bad for users waiting, and to not have too much load on servers with lots of concurrent users).

We can certainly add argon support (news to me that go stdlib now has it, thought it was still would require an extra dependency), but I don't think we should change the default, bcrypt is still the best option.

@GreyXor
Copy link
Contributor Author

GreyXor commented Aug 17, 2025

Totally fair point, Argon2id shows its real strength when you can allocate more time and memory, which isn’t always practical in web contexts. I ran some benchmarks that show the login with the default settings takes 0.026 seconds on my system. (first login, without cache). What do you think about ?

@mholt
Copy link
Member

mholt commented Aug 18, 2025

We can allocate more memory but probably not much more time.

Is there any way to tune it so that it takes at most 1/2 second to converge?

@GreyXor
Copy link
Contributor Author

GreyXor commented Aug 19, 2025

We can allocate more memory but probably not much more time.

Is there any way to tune it so that it takes at most 1/2 second to converge?

Yes, there's these 4 new parameters:

--argon2id-time
    Number of iterations to perform. Increasing this makes
    hashing slower and more resistant to brute-force attacks.
--argon2id-memory
    Amount of memory to use during hashing.
    Larger values increase resistance to GPU/ASIC attacks.
--argon2id-threads
    Number of CPU threads to use. Increase for faster hashing
    on multi-core systems.
--argon2id-keylen
    Length of the resulting hash in bytes. Longer keys increase
    security but slightly increase storage size.

Default values(match the recommended OWASP) are:

    Memory: 46 MiB
    Iterations: 1
    Parallelism: 1
    Salt length: 16 bytes
    Key Length: 32 bytes

I didn't experience any slowness with this. My login was instant.

@mholt
Copy link
Member

mholt commented Aug 20, 2025

I didn't experience any slowness with this. My login was instant.

Just to confirm, was this a very first login after starting the server? Caddy caches the results for future requests.

Good chance this will get merged, but maybe after the next release (within a day or two I hope, if my computer is stable).

@GreyXor
Copy link
Contributor Author

GreyXor commented Aug 20, 2025

I didn't experience any slowness with this. My login was instant.

Just to confirm, was this a very first login after starting the server? Caddy caches the results for future requests.

Yes, this is for the very first login without any cache.
With the recommended by OWASP (default configuration), the first login completes in just 0.028 seconds.

Edit: Additional benchmarks (measured using time curl -u Bob:hiccup http://127.0.0.1:8080/):

#default setting
caddy hash-password --algorithm "argon2id"
0.028 seconds

caddy hash-password --algorithm "argon2id" --argon2id-time 10
0.189 seconds

caddy hash-password --algorithm "argon2id" --argon2id-time 50
0.926 seconds

caddy hash-password --algorithm "argon2id" --argon2id-keylen 64 --argon2id-threads 8 --argon2id-time 300
1.309 seconds

@francislavoie
Copy link
Member

Makes sense to me.

I'd been thinking we should lower the default bcrypt to 10 instead of 14. Maybe we do that now too.

14 is kinda ridiculously high lol, 12 or 13 is usually good for most apps with a traditional DB, but for basicauth the needs are not so equivalent and we had to do caching which is kinda cheating, to make it not hurt a lot.

@GreyXor
Copy link
Contributor Author

GreyXor commented Aug 21, 2025

Sounds good to me. If there’s agreement, I’d be happy to include it in my PR.

@steffenbusch
Copy link
Contributor

If the default bcrypt cost is changed, users of Caddy should be made aware of this change in the next release notes.

Let's assume you have existing bcrypt hashes in you Caddy configuration, all of them with costs of 14. Now the default costs is changed to 12 and I guess the "FakeHash" is changed to 12 as well. This would allow a user enumaration because "fakehash" used for non existing users will be faster than a wrong password for an existing user with bcrypt cost of 14.

@francislavoie
Copy link
Member

Good point, to deal with that, we should probably ship fake hashes at each of the common cost factors and use the highest cost configured as the one to test against for unknown users.

@GreyXor
Copy link
Contributor Author

GreyXor commented Aug 25, 2025

Yes, if we change the default bcrypt cost, then the fakehash must be updated in sync, otherwise there’s a measurable difference in response times that could lead to user enumeration.

I think this is a separate concern from Argon2id support. To keep the PR clean and focused, it would make sense to handle the bcrypt cost change (and fakehash adjustment) in a dedicated PR. How that sound for you @francislavoie ?

@francislavoie
Copy link
Member

Yeah that's probably fine. Was just thinking it would make sense to sync the rough cost (time taken) from these argon settings to bcrypt's, but it's not an absolute blocker

@GreyXor
Copy link
Contributor Author

GreyXor commented Aug 25, 2025

Absolutely, that makes a lot of sense. If I have some bandwidth this week, I can open a separate PR to handle that change.

In the meantime, let me know if there’s anything else needed to get this branch ready for merge.

@GreyXor
Copy link
Contributor Author

GreyXor commented Sep 19, 2025

Hello @mholt @francislavoie
Do we need anything else before merging? Thanks :)

@mholt
Copy link
Member

mholt commented Oct 3, 2025

@GreyXor Sorry for the delay; can you rebase to resolve the conflicts?

I am not super in favor of changing the default bcrypt cost (even in a separate PR) -- it adds a lot of complexity for very little / no gain, since the cache prevents slowdowns after the first login.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants