example.org => [Private key for example.org]
acme.com => [Private key for acme.com]
A U2F device should generate a new ECC key pair for each service it registers with. When authenticating, the device should use the previously generated private key for that service. This is trivial, but gets more complex as we add more requirements:
Without any additional requirements, all we need is a key-value store on the device.
example.org => [Private key for example.org]
acme.com => [Private key for acme.com]
We want to allow devices to store multiple credentials per service. For example, a user might have more than one Gmail account. To solve this, we associate each credential with a key handle instead of with a service. During registration, the device sends a key handle which is kept by the service. When authenticating, the service sends the key handle back to the device.
KeyHandle[0x21AE9B] => [Private key 1 for example.org]
KeyHandle[0xFBD308] => [Private key 2 for example.org]
KeyHandle[0x18C77B] => [Private key 1 for acme.com]
The implementation described above works just fine, but it comes with a limitation. Each credential consists of a private key along with metadata associated with that credential, which all require storage space. This puts an upper limit to the number of credentials that can fit on any one device. To avoid this limitation, YubiKeys use the following approach:
Note
|
Updated Feb 2020 to reflect current YubiKeys. The following applies to any YubiKey or Security Key by Yubico with a firmware version of 4.4 or greater ( this includes any YubiKey FIPS device). |
During credential registration, a new key pair is randomly generated by the YubiKey, unique to the new credential. The private key, along with some metadata about the credential, is encrypted using authenticated encryption with a master key. This master key is unique per YubiKey, generated by the device itself upon first startup, and never leaves the YubiKey in any form. For FIDO2 capable YubiKeys, this master key is re-generated if FIDO2 RESET is invoked, thereby invalidating any previously created credentials.
The encryption used for each credential is AES-256 in CCM mode, which allows us to cryptographically tie things like the AppID to the private key, ensuring that the credential can only ever be used with the correct RP. The encrypted (and authenticated) data then forms the 64-byte key handle, which is sent to the server as part of the registration flow, to be stored by the RP for later.
For authentication, the RP returns the key handle to the YubiKey. Here it is decrypted to re-form the private key which is needed to sign the challenge to complete the authentication. Due to the authenticated encryption used, we know that the private data has not been altered in any way, and can verify that the credential is being used with the correct AppID.
By using this approach, the YubiKey does not need to store any per-credential data, and can thus register and use any number of credentials. This is true for both U2F and for WebAuthn "non-resident keys". For WebAuthn resident keys, internal storage must still be used.