Why does FIDO2/WebAuthn need a separate key-pair for each site?

Recently I did some reading on the FIDO2/WebAuthn standard. I understand that FIDO2/WebAuthn generates a separate key-pair for each web-site (domain), and only allows a specific key-pair to be used for authentication on the same web-site (domain) who originally generated/registered that key-pair with the browser/authenticator, in order to prevent MITM and Phishing attacks.

But: Wouldn't it be sufficient to simply make the RPID (Relying Party ID, i.e. domain name) a part of the "challenge", i.e. the challenge would effectively become "RPID + random_bytes", and require the browser/authenticator to verify that the RPID in the challenge is equal to the current web-site (domain), before signing? If a mismatch is detected, someone obviously tried to trick us into signing a "foreign" challenge, which indicates a MITM/Phishing attack, so the authenticator would simply refuse to sign the challenge in that case.

Big advantage would be that we don't need tons of separate key-pairs for the same user. Most FIDO2 devices can only store a few...
Last edited on
Don't such devices generate the per-site key on-the-fly using the master key and the domain name as seeds?
Last edited on
Not sure. Maybe it could be implemented that way.

But, for example, the YubiKey web-site says:
the YubiKey 5 can hold up to 25 discoverable credentials in its FIDO2 application.

Sounds like it stores each key-pair (credential) separately, and there is a limit of how many you can have.

...which, of course, is good for their business model 😂

Question is, why do we need separate key-pairs anyway, as long as we properly verify that the "challenge" came from the correct site?

Remember: Standard TLS-Client-Auth can use a single key-pair (X.509 certificate) for any number of sites.

The only real argument that I have heard is that using a separate key-pair for each site might be a protection against tracking...

(but there are so many other ways for tracking, it cannot be the only reason)
Last edited on
One reason I can think of is that it lets you revoke a key without revoking all of them.
However, I don't think that FIDO2/WebAuthn has any kind of "revocation" mechanism at all 😨

It's the "relying party" (web-service) that will remember your public-key, which was generated during the registration ceremony, and assign that key to your account, in their database. From this moment on, you'll be able to log in with the corresponding private-key.

The only way to "revoke" a public-key would be to log in to your account, and de-register the public-key; provided that the "relying party" (web-service) offers this feature. And you would have to do this witch each "relying party", where that key is used, separately.
Last edited on
Right. If it was a single private key for all sites, you would have no way to tell a site to change its public key without telling every other site to do the same. Although, I suppose there's no reason to do that but to replace a lost device, so I don't know.
If it was a single private key for all sites, you would have no way to tell a site to change its public key without telling every other site to do the same.

I don't see this!

To my understanding, WebAuthn is totally bilateral: When I register my authenticator with a certain web-site (relying party, RP), my authenticator sends a public-key to the RP server. Usually there will be a "fresh" key-pair for each RP, but the RP really has no way to check this. In theory, we could use the same key-pair for all sites. Anyway, the RP server will remember the submitted public-key and use it to verify my future logins (challenge signatures). That's it. No other web-site has any knowledge about this bilateral registration. And, much unlike X.509 certificates, there also is no "central" authority that needs to approve my public-key, or that could "revoke" my key-pair.

Therefore, I think that the only way to "revoke" a public-key in the context auf WebAuthn would be to tell the specific RP that it should no longer accept the previously registered public-key for my logins – provided that the RP offers such an "un-register" functionality. But this certainly will not effect any other RP's where that same public-key might still be in use. Regardless of whether we use the same key for all sites, or whether we use a different key for each site, we would have to "revoke" the key(s) separately from each site...

(e.g. when the authenticator was lost, stolen or it broke)


There is one important thing I have learned in the meantime: Most, if not all, authenticators do not really generate (and store) a separate key-pair for each site. Instead, they will "(re)compute" the private-key for each site "on the fly" whenever it is needed, based on a single "master" key (built into the device), an individual seed, and the site's RPID. This makes having a separate key for each site very cheap.

Only special "residen" keys are actually stored in the authenticator.

So, apparently, there is just no reason to re-use the same key-pair on multiple sites, since per-site keys come "for free" 🤔
Last edited on
You're still not getting it. If the device used the same key for every site (as in, it had room for just one key), you would not be able to rotate key for any site without rotating it for all of them. If you're using 2FA on Google and Github, and you generate a new key for Google (thus overwriting the previous key) if you don't also update the key for Github, then your 2FA is locked out.
If the authenticator could store only one key, then unavoidably we had to use the same key for all sites.

But that's not true the other way around: If the authenticator can store multiple keys (e.g. two or more), then we still could happily use just one key for all sites. And, if we ever have to "rotate" that key, we simply generate a second (new) key on the authenticator, switch from the "old" key to the "new" key on all sites, one by one, and finally we can erase the "old" key from the authenticator.

My original question was: Why does WebAuthn always generate n distinct key-pairs for n sites?

From what I understand now, the solitary reason why this is done actually is to avoid "tracking" the user based on its public key 🕵️

Also, as pointed out before, even though the authenticator uses n distinct key-pairs (one for each site), they are not actually stored on the authenticator in practice, but are derived from a single "master" key (and from the site's "credential id") when they are needed.

(initially, I didn't know that key-pairs can be derived "on the fly" like this, as the spec doesn't make this clear!)

But it means that the authenticator can have an "unlimited" number of different key-pairs. There is no limit we have to worry about 😊
Last edited on
I can think of one limitation. Suppose that the user wants to be able to rotate keys per site. If the key is generated on-the-fly from the concatenation of just the master key and the site, then there's no way to do that. There would need to be an additional source of entropy for the KDF that's independent of the master key and the site, and the device would need to remember the value of this source and associate it with the site, to be able to generate the correct key next time. The input can be as simple as a 32-bit integer that's incremented each time the key is rotated. I don't know how much memory these devices contain, but nevertheless, it's certainly not an unlimited number of key pairs.
That said, 25 is rather small. It suggests the device holds at most a handful of kilobytes of storage.
During the registration process, the authenticator generates a new key-pair and a so-called "credential id". The pubic-key together with the "credential id" are sent to the server and stored there. How exactly the authenticator generates the key-pair and the corresponding "credential id" is up in the air. During the authentication process, the server provides the stored "credential id" to the client/authenticator.

The spec describes the whole process as if the private-key is stored locally in the authenticator, and the "credential id" will only ever be used to select the correct key from the local storage; pretty much like a database id (or slot index). But, to my understanding, that's not how it is actually implemented! In fact, the "credential id" is used as a random seed. A fresh seed will be generated by the authenticator for each registration. Also, the authenticator will use the "credential id" plus the RPID (domain name) in combination with its single "master" key to compute the individual key-pair. The key-pair (private-key) is not stored at all, but will be re-computed whenever it is needed!

This means there is no limit on the number of "normal" keys an authenticator can have, because it does not need to store anything per key. It still is possible to renew the key for a specific site (RP), simply by generating and registering a new random "credential id" for that site.

The special "resident keys" are a different story, though. Those don't have a "credential id" to work with at all, because they allow a login without entering a user-name first. And, therefore, they must be stored fully on the authenticator. The limit of 25 keys that the YubKey web-site mentions applies to those "resident keys" only. AFAIK, there is not much use of "resident keys" at the moment.

(what makes things confusing is that the spec and most tutorials describe the purpose of the "credential id" differently from what it is used for in real-world authenticators; after understanding what authenticators really do, the significance of "resident keys" becomes clear)
Last edited on
Topic archived. No new replies allowed.