did:requestToken: aka insecure PAKE

@icidasset and I were discussing some aspects of our new server implementation, and hit upon this totally random idea that might be totally obvious and already implemented all over the place, but I haven’t seen it before so writing it down here.

NB, this is obviously just “Insecure PAKE” BUT really really simple and usable in place of “we emailed you a short opaque code.” I have to assume that PAKE isn’t widely used for this because having a key is meaningless unless you’re signing something. We have UCANs, so it becomes meaningful?

For proof-of-identity, our first version is going to use the tried-and-true email challenge / magic link approach: your account has an email address associated with it, we generate a random code, send you an email with that code, you give us back that code proving that you have access to the email inbox, we give you access to your account. Viola!

We were discussing this in the context of did:odd, because did:odd is effectively the same thing, where “did:odd” means “your trust root is our request token database,” and it occurred to us that instead of sending a temporary token, we could just send the full ed25519 key, since they’re short enough. BUT, they’re still kinda long and weird to read.

SO - and this is not particularly surprising - what if instead of sending the full private key, we do something in between:

First, the user’s agent makes a request to reset the account:

POST /auth/requestToken?account=alice@example.com

We generate a new ed25519 key, zAlice, and a short (4-6 digits) random token, tokAlice. We divide zAlice by tokAlice to get partAlice:

partAlice = zAlice / tokAlice

(obviously zAlice needs to be divisible by tokAlice; how to generate the two numbers so that that’s true is an exercise left to the reader. Alternate approaches that rely on encoding remainders into tokAlice is left as an exercise to the reader)

We then return partAlice to the user agent, which will effectively be a client key. Next, we send Alice tokAlice by email, to alice@example.com. Alice receives the 4-6 digit token, which she provides to her user agent.

The user agent multiplies tokAlice by partAlice and now has possession of the full private key:

zAlice = tokAlice * partAlice

which can then be used to sign a UCAN using did:key:zAlice – this can be used to make the request to update Alice’s account. zAlice is only temporarily stored on the server, and has a short expiry associated with it in our database to prevent misuse in case partAlice or tokAlice aren’t removed from Alice’s computer or intermediate systems.

This approach has the following advantages over traditional magic link schemes:

  1. We can use a full private key to sign the UCAN, but with the UX benefit that Alice only needs to interact with a short token.
  2. The token emailed to Alice is only usable by a specific user-agent. Anyone who intercepts tokAlice is unable to use it without also compromising Alice’s user agent.
  3. The client token partAlice is useless without tokAlice. Obviously, tokAlice could be brute-forced, but the combination of a limited lifetime for zAlice plus rate limiting means that we can prevent brute-force attacks.
  4. It’s freaking trivial to implement. But maybe that makes it ridiculously insecure? How insecure is it relative to emailing an opaque token, though? :man_shrugging:


We could of course use a better PAKE scheme (OPAQUE, CPace as approved by the IETF), or even just the token directly as a key, but all of the former options I’m aware of are designed for securing passwords that are intended to be used long-term and are therefore much more complex, and doing the latter increases complexity without meaningfully improving security. But, maybe that doesn’t matter?

I recognize the train of thought though! We’ve been down similar roads before, so there must be a little brain hook for this kind of design :slight_smile:

A core idea in UCAN (and capabilities) is to transfer authority without transferring keys. The proposal makes me nervous because the key being sent around can be intercepted. Using UCAN delegation works around this by making authority time limited, scoped to certain resources and actions, and revocable.

The (un?)PAKE that you described reminds me a little bit like ZeroWallet.

10-20 bits of security is easily crackable, and the security is equivalent to the 6-digit code.

Key pairs are extremely cheap to produce, and browsers have the ability to keep them nonextractable. Instead of sending the secret key around, it’s more secure, gives the user more agency, and has fewer moving parts to use something like Irakli’s UCAN cosigner method.

100% – I think I probably wasn’t clear enough above; the use case is explicitly the “magic link” email, where we’re going to send you a code that’s short enough to copy-and-paste into a client, and only for situations where we have to send you a verification code, and where we have enough control that we can guarantee that you’re not going to be able to brute force it (by limiting the number of attempts to ~3). The main difference is rather than using the code as an opaque token, we’re transforming it into a key so that all of our auth can be done using UCANs.

I’m not aware of a different way to do email verification (short of using OIDC)? There is a question as to what length of challenge we’re comfortable sending.

The nice think about an approach like this is that it does verify that the client that requested the token is the client that’s using it (without doing anything complicated like OAuth’s exchange token), and unlike a 6-digit code, gives us the ability to use the code as a did:key (although I suppose we could just use the code as the key :upside_down_face:).

The main constraints here are:

  1. Human manageable token, chosen by us.
  2. User opens email with token, pastes or types it into their UA (maybe clicks a link, if we give the UA a way to specify the endpoint).
  3. Very low implementation complexity. I think we’d be doing something wrong if our email confirmation code implemented a complex secret sharing system, because the likelihood of getting something wrong in the implementation of a complex system is higher risk than someone brute-forcing codes, especially when we have lots of tools at our disposal to prevent brute-force attacks (But maybe this is an incorrect assumption?).

Also worth stating that overall I’d prefer to do email verification by OIDC where possible, but that’s further down the line.

Yeah abolutely: this is a great use case for the fct field in a UCAN. This helps you bind a temoprary client-generated DID in a single round trip. The security of using fct is higher than sending a private key over the wire. While both have their initial session security rooted in the email challenge, the fct method’s security hole is limited to “whoever uses the out of band code first”. Once a UCAN with the challenge code hits the server, the security hole is closed.

In terms of implementation complexity: we’ve built this flow with email previously, and it’s very easy to extract the challenge from a UCAN :+1:

This is how also how AWAKE works under the hood, but with a QR code or user typing the code in instead of email. I’m happy to chat during the week if walking through the flows would be helpful (we have some fancy new things that help keep these kinds of flows secure), but TL;DR for the end user they look the same.

Totally: we want to avoid needing to use BLS or SSS or something :100: The UCAN method mentioned above only has a static shared secret (the code challenge — same as the transferred secret key method in the initial post). Everything else is asymmetric and made out of commodity components like the WebCrypto API or OpenSSL.

Aha, that makes sense! It’s tricky keeping all the functionality in memory. fct ftw!

1 Like

Yeah absolutely :100: We have buckets of building blocks, but after n years of work, they have their own little niches and ecosystems. Always happy to surface stuff as they come up so that you don’t have to hold it all in your head!