diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-14 03:01:58 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-17 12:43:09 +0200 |
| commit | 0370d001a4e74d2af0809a3e1eb5ada699b1bca2 (patch) | |
| tree | 4eca51060cc94c04856c59765886523b70583b7c /docs | |
| parent | 9d1d4a7af5844f50f168ff00818382ffe57c3452 (diff) | |
| download | meshbay-0370d001a4e74d2af0809a3e1eb5ada699b1bca2.tar.gz | |
fix(hub): the password verifier is Argon2id 64 MiB, and a hash's version names its parameters
`pw_version` 4: Argon2id 64 MiB, t=3, lanes=4 — RFC 9106's second recommended
setting. A v3 hash (256 MB) still verifies at its own parameters and is
rewritten at the new ones on the next sign-in, through the rehash path that
already existed.
Why not more. The verifier matters against an offline attacker holding the
database; online guessing is bounded by the sign-in lockout. That attacker pays
the client's 600 000 PBKDF2-SHA512 iterations and the hub's Argon2id per guess,
since `auth_key` is 256 bits and cannot be searched directly. Memory above
64 MiB multiplies that cost by a constant — at most 16 at 256 MB, less with
PBKDF2 counted — while the hub pays the same memory at every sign-in, one
derivation at a time. Measured on meshbay.org: 450 ms at 256 MB, 105 ms at
64 MiB, so a burst of sign-ins clears about four times faster.
Changing the current version exposed a latent lockout. `hash_password` always
used the current version's parameters, while the raw-password scheme recorded
`pw_version = 2` — harmless while versions 2 and 3 shared their parameters,
and with version 4 every legacy registration and v1→v2 rehash would have
stored a 64 MiB hash labelled 256 MB, which nothing could then verify. Seventeen
tests caught it. `hash_password` now takes the version it is hashing for.
The OpenSSL deadlock between two concurrent `lanes=4` derivations is the same at
64 MiB, so Argon2 stays on its single worker. The loop-stall test measures
against a v3 hash, because half of a 45 ms inline derivation is too close to
scheduling noise to be a reliable bound.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LcF3QKWii7uQ2kSyXErzCt
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/MESHBAY_DESIGN.md | 19 |
1 files changed, 18 insertions, 1 deletions
diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md index 05f6d73..92f56ef 100644 --- a/docs/MESHBAY_DESIGN.md +++ b/docs/MESHBAY_DESIGN.md @@ -736,9 +736,26 @@ the index. | Parameter | Value | |---|---| | Node keystore KDF | Argon2id **256 MB**, t=3, lanes=4 — recorded per envelope, so raising it does not orphan existing keystores | -| Hub password verifier | Argon2id **256 MB**, t=3, over the client-derived `auth_key` — `pw_version` migrates transparently on next login | +| Hub password verifier | Argon2id **64 MiB**, t=3, lanes=4 (`pw_version` 4), over the client-derived `auth_key` — RFC 9106's second recommended setting. An older hash is verified at its own version's parameters and rewritten at the current ones on the next sign-in | | Browser bundle key | Argon2id **128 MB**, t=3, p=1 | | Browser `auth_key` | PBKDF2-SHA512, **600 000** iterations | + +**Why the hub verifier is 64 MiB and not more.** What it protects against is an +offline attacker holding the database; online guessing is bounded by the sign-in +lockout (§7.7), where Argon2's cost plays no part. That attacker pays, per guess, +the client's 600 000 PBKDF2-SHA512 iterations *and* the hub's Argon2id, because +`auth_key` is 256 bits and cannot be searched directly. Above 64 MiB the memory +multiplies the attacker's cost by a constant factor — at most 16 at 256 MB, less +once PBKDF2 is counted — which moves a weak passphrase from cracked-soon to +cracked-later and a strong one from out of reach to out of reach. On the hub the +same memory is paid at every sign-in, one derivation at a time (§13.5b, **AV9**): +measured on meshbay.org, 450 ms at 256 MB against 105 ms at 64 MiB. The +passphrase floor is what separates the two cases, not the verifier. + +**The stored `pw_version` always names the parameters the hash was made with.** +Verification reads them from it, so `hash_password` takes the version it is +hashing for; the legacy raw-password scheme stays at version 2 and the +`auth_key` scheme is every version from 3 up. | Chunk cipher | AEAD, 1 MB chunks, per-chunk key by HKDF | | Chat nonce | 96 random bits per message, never a counter | | Invite / pair codes | 40 bits, Crockford base32, single use, stored as `sha256` | |