From 0370d001a4e74d2af0809a3e1eb5ada699b1bca2 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Mon, 14 Sep 2026 03:01:58 +0200 Subject: fix(hub): the password verifier is Argon2id 64 MiB, and a hash's version names its parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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 Claude-Session: https://claude.ai/code/session_01LcF3QKWii7uQ2kSyXErzCt --- docs/MESHBAY_DESIGN.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'docs/MESHBAY_DESIGN.md') 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` | -- cgit v1.2.3