Skip to content

fix(auth): store passwords as argon2id instead of unsalted MD5 - #3

Merged
inkly merged 1 commit into
mainfrom
fix/argon2id-passwords
Oct 7, 2026
Merged

inkly merged 1 commit into
mainfrom
fix/argon2id-passwords

Conversation

@inkly

@inkly inkly commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator

Passwords were stored as an unsalted MD5 in hex (as IceWhale's service still does): a stolen user.db goes back to a wordlist in minutes.

What changes

  • New accounts, password changes and the -ru reset store an argon2id hash (PHC string, 19 MiB, 2 passes, 1 lane: the OWASP minimum). Parameters are read back from the stored string, so they can be raised later; NeedsRehash says when a hash should be made again.
  • An existing account keeps signing in with its MD5. Its first successful sign-in replaces the hash, with nothing asked of the owner. A wrong password changes nothing.
  • The replacement only lands if the row still holds the hash the sign-in read, so a password change that races a sign-in is not undone by the stale write.
  • At most two hashes are computed at once. Not every endpoint that checks a password is rate limited, and 100 parallel requests would otherwise be about 2 GB.
  • Stored values are parsed strictly (parameters printed back and compared, bounds on memory, passes, lanes, salt and key sizes).

Downgrade
Once an account has signed in, user.db holds argon2id, which older releases (and IceWhale's) cannot verify. To roll back, set a new password with the old binary: casaos-user-service -ru -user <name>. README says so; the release notes will too.

Tests
Unit tests for hash/verify, salts, long and non-ASCII passwords, legacy MD5, other parameters, malformed values and concurrent use; route tests for the sign-in upgrade, wrong password, registration, password change and the stale-write race. go test ./... and GOOS=linux go vet ./... pass.

New accounts, password changes and the -ru reset now store an argon2id hash
(PHC string, 19 MiB, 2 passes, 1 lane). An account that still holds the legacy
MD5 keeps signing in, and its first successful sign-in replaces the hash; the
swap is conditional on the row still holding the hash it read, so a password
change racing a sign-in is not undone.

Hashing is limited to two at a time so parallel requests on endpoints without a
rate limit cannot ask for gigabytes.

A release older than this one cannot verify an argon2id hash: roll back with
casaos-user-service -ru -user <name>.
@inkly
inkly merged commit 576c2a0 into main Oct 7, 2026
1 check passed
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.

1 participant