A passphrase that is the username makes the identity public #238

Merged
paulhitt merged 1 commits from fix/a-passphrase-that-is-the-username into main 2026-09-09 00:09:21 +00:00
paulhitt commented 2026-09-08 21:41:21 +00:00 (Migrated from gitlab.com)

Reported by a user: the passphrase could be set to the username.

This is not the usual weak-password argument, and the usual
reasoning does not reach it. For a relay-local account the KDF salt is
HKDF(lowercased(username)) — a pure function of a name that is public by
design, because it is how people find each other and because a new
device has to derive it offline before it can talk to anything. The
account key is PBKDF2(passphrase, salt), and the address is that key's
public half.

So when the passphrase IS the username, every input is public. Deriving
the identity is one PBKDF2 run rather than a search: the 1.3 million
iterations buy about a second, once, against a guess that is already
correct. Whoever runs it can claim the address and sign as that person
— and the thing they take is the one this product says no server can
take away.

Refused at sign-up, through the same list that already explains why the
button is disabled, so the refusal cannot be silent. It gives the real
reason rather than generic password advice, because the real reason is
what makes it worth retyping.

Near misses are refused too. "sunshine1" against the username
"sunshine" is ten guesses; a search space small enough to run over lunch
is not meaningfully different from none. It stops at 8 added characters,
so a memorable phrase that happens to contain the username is still
allowed — pushing people off passphrases toward worse ones would cost
more than it saves.

Normalisation matches the KDF's exactly. The salt lowercases and
trims the handle, so "Sunshine" derives the same key as "sunshine"; a
check that disagreed would refuse the safe spelling and permit the
unsafe one.

KNOWN GAP, not fixed here: this is a client-side check and therefore
bypassable, and it does nothing for accounts that already exist. The
authoritative gate belongs in the signup handler, which is not in any
local checkout — flamenet-api has no messenger routes and
flamenet-accounts-hosting is deploy-only here. Android needs the same
rule. Both are follow-ups.

6 new tests; 764 XCTest + 61 Swift Testing green.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com

Reported by a user: the passphrase could be set to the username. This is not the usual weak-password argument, and the usual reasoning does not reach it. For a relay-local account the KDF salt is HKDF(lowercased(username)) — a pure function of a name that is public by design, because it is how people find each other and because a new device has to derive it offline before it can talk to anything. The account key is PBKDF2(passphrase, salt), and the address is that key's public half. So when the passphrase IS the username, every input is public. Deriving the identity is one PBKDF2 run rather than a search: the 1.3 million iterations buy about a second, once, against a guess that is already correct. Whoever runs it can claim the address and sign as that person — and the thing they take is the one this product says no server can take away. Refused at sign-up, through the same list that already explains why the button is disabled, so the refusal cannot be silent. It gives the real reason rather than generic password advice, because the real reason is what makes it worth retyping. Near misses are refused too. "sunshine1" against the username "sunshine" is ten guesses; a search space small enough to run over lunch is not meaningfully different from none. It stops at 8 added characters, so a memorable phrase that happens to contain the username is still allowed — pushing people off passphrases toward worse ones would cost more than it saves. Normalisation matches the KDF's exactly. The salt lowercases and trims the handle, so "Sunshine" derives the same key as "sunshine"; a check that disagreed would refuse the safe spelling and permit the unsafe one. KNOWN GAP, not fixed here: this is a client-side check and therefore bypassable, and it does nothing for accounts that already exist. The authoritative gate belongs in the signup handler, which is not in any local checkout — flamenet-api has no messenger routes and flamenet-accounts-hosting is deploy-only here. Android needs the same rule. Both are follow-ups. 6 new tests; 764 XCTest + 61 Swift Testing green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
paulhitt commented 2026-09-08 21:41:22 +00:00 (Migrated from gitlab.com)

assigned to @paulhitt

assigned to @paulhitt
paulhitt commented 2026-09-09 00:09:21 +00:00 (Migrated from gitlab.com)

mentioned in commit 9af8e5b1b8

mentioned in commit 9af8e5b1b820ffc28d192b9815cd05c25ae0af7e
paulhitt (Migrated from gitlab.com) merged commit 9af8e5b1b8 into main 2026-09-09 00:09:21 +00:00
paulhitt commented 2026-09-09 18:59:54 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
Sign in to join this conversation.