(Note: What follows is fresh and untested. It also breaks the rule "Don't roll your own crypto". It needs peer review and we would love to hear from you!)
The goal is to design a login system with authentication processes leaking minimal information. In particular we want:
- Full at rest deniability of usernames. That is, we should not be able tell from looking at the database if a particular username is or is not associated with a user.
- Full in use deniability of usernames (for registrations, login attempts, credential changes), except when the username is an email address used for magic links.
- Full at rest deniability of login activity. (For example, no login counts, timestamps, IP addresses, etc. stored in the database.)
We also assume a passive snooper (rogue employee, government, cloud provider, etc.) that has both at rest read access to the databases, and in use read access to server activity. Even with this assumption, we want:
- At rest and in use security
- The user uses his email address as an easy-to-remember username
- The user should be able to change his password and email address
- The user can opt-in to magic link verification (on top of the password) for extra security
e
is the user's email (used as a username)p
is the user's passwords
is the user's secret, the concatenatione || p
m
is the user's mnemonic, 120 bits of entropy generated at registrationc
is a challenge, assumed to be the current time in nanosecondsH
is SHA256priv(a)
is the Ed25519 private key derived from usinga
as seed, using PKDF2 as a key stretcher if needed.pub(a)
is the Ed25519 public key derived from usinga
as seed, using PKDF2 as a key stretcher if needed.E(b, pub(a))
is the encryption ofb
withpub(a)
S(b, priv(a))
is the signature ofb
withpriv(a)
- For every
s
, the server stores a mapping from the keyk = H(e) ⊕ H(H(s))
(here⊕
is XOR) to a struct containing:
pub(s)
E(m, pub(s))
magic
, a signed boolean indicating if magic link security is enabledc_last
, the last used challenge
- The user sends
k
,c
andS(c, priv(s))
to the server. Ifmagic
is true, the user is expected to also sende
andH(S)
. - The server checks:
- that
c
is within 3 minutes of the current server time (if not, the server returns the current server time) - that
S(c, priv(s))
is valid - that
c > c_last
(to guarantee uniqueness ofc
) - if
magic
is true, that the equalityk = H(e) ⊕ H(H(s))
holds
- If the above checks all pass the server returns
E(m, pub(s))
. The user can then getm
withpriv(s)
.
Notes:
- If
magic
is true, an attacker cannot provide a fakee
by construction. - Multiple encrypted mnemonics can be stored under the same key
k
. - If either
e
orp
changes, a new mapping is created. The old mapping is deleted. - When creating a new mapping, never overwrite existing mappings. (Knowledge of
k
should not allow an attacker to overwrite the mapping.) - If magic link is never enabled,
e
can be any string (not necessarily a valid email address). - At rest, the database stores
k
,pub(s)
,E(m, pub(s))
,m
andc_last
:
k
,pub(s)
andE(m, pub(s))
are random pieces of data, leaking no critical informationk
,pub(s)
andE(m, pub(s))
can be faked, providing deniabilityk
,pub(s)
andE(m, pub(s))
can be length validatedc_last
can be flushed if it is less than the current time minus 3 minutes
- User requests disclose
k
,c
andS(c, priv(s))
, which leak no critical information. (Noise can be added toc
to not leak the user's precise clock.) Ifmagic
is true,e
is necessarily disclosed (to send the magic link) andH(s)
leaks no critical information. - If the user attempts to log in without 2FA when
magic
is true, the signedmagic
is returned, verified by the client, and login is retried, sending the appropriate 2FA data.