---
url: 'https://www.corbado.com/blog/security-key-capabilities'
title: 'Security Key Capabilities: CTAP Feature Comparison'
description: 'Which security key supports PRF, credProtect, largeBlob and how many passkeys? A capability reference for 325 FIDO2 keys, built from CTAP getInfo.'
lang: 'en'
author: 'Vincent Delitz'
date: '2026-08-24T13:50:04.440Z'
lastModified: '2026-08-24T14:26:55.739Z'
keywords: 'security key capabilities, fido2 security key comparison, ctap 2.1 features, hmac-secret prf support, credprotect security keys, largeblob security key, discoverable credential capacity, authenticatorGetInfo'
category: 'WebAuthn Know-How'
---

# Security Key Capabilities: CTAP Feature Comparison

## Key Facts

- **The capability data for security keys is public.** The FIDO Alliance Metadata Service
  ships the `authenticatorGetInfo` response of every certified key. All numbers here come
  from its 325 roaming FIDO2 authenticators.
- **hmac-secret, the basis for WebAuthn PRF, is almost universal at 94%.** credProtect sits
  at 76%, credBlob at 46% and largeBlob at 41%. Only 59% declare CTAP 2.1.
- **Storage capacity is where the field differs most.** Discoverable credential slots range
  from 6 to 662, median 100.
- **`bioEnroll: false` does not mean the key has no fingerprint sensor.** For options a user
  sets up, false means supported but not configured yet.
- **`maxCredentialCountInList` caps credential IDs per request, usually at 8.** Nine HID
  Crescendo variants cap it at 1, so 20 credentials mean 20 requests.

## 1. Datasheets do not tell you which CTAP features a security key supports

A product page gives you the price, the form factor and the words "FIDO2 certified". It does
not tell you whether the key can serve PRF, how many passkeys fit on it or what happens when
your `allowCredentials` list gets long. Those answers decide whether your login works on the
hardware your users already carry. So developers keep asking us which manufacturer implements
which CTAP and WebAuthn feature.

The answer is public. Every FIDO2 authenticator describes itself in a CTAP command called
`authenticatorGetInfo`: protocol versions, extensions, options, credential capacity, PIN policy
and transports. The FIDO Alliance publishes that response for every authenticator listed in its
Metadata Service.

This article turns that data into a reference. It covers all 325 roaming authenticators in
the BLOB, with a comparison table for the keys that teams deploy most often, the numbers
across the whole field and the rules you need to read those numbers correctly.

If you are choosing hardware rather than building against it, our
[best FIDO2 hardware security keys](https://www.corbado.com/blog/best-fido2-hardware-security-keys) article is the
better starting point. It sorts keys by price, form factor and protocol support.

## 2. Where the numbers in this article come from

### 2.1 What authenticatorGetInfo returns

`authenticatorGetInfo` (command `0x04`) is the first thing a client sends to a FIDO2
authenticator. It happens inside the browser or the platform, over USB or NFC, before your
`navigator.credentials` call reaches the key. The answer is a CBOR map that describes the
device, and the client uses it to decide what it is allowed to ask for.

```mermaid
sequenceDiagram
    autonumber
    participant RP as Your Relying Party
    participant B as Browser / Platform
    participant K as Security Key

    RP->>B: navigator.credentials.create() or .get()
    Note over B,K: CTAP2 over USB HID, NFC or BLE
    B->>K: authenticatorGetInfo (0x04), no parameters
    K-->>B: CBOR map: versions, extensions, options, limits
    Note over B: Client decides what it may ask for:<br/>PRF only if hmac-secret is listed,<br/>list chunked to maxCredentialCountInList
    B->>K: authenticatorMakeCredential (0x01) or GetAssertion (0x02)
    K-->>B: Attestation or assertion
    B-->>RP: PublicKeyCredential
```

Step 5 happens where you cannot see it. The browser knows which key is connected. Your relying
party does not. If the key does not list `hmac-secret`, the browser never asks it for PRF,
whatever your code requested, and you get back a credential with no PRF result and no error to
log. `credMgmt` works the same way: a management tool that finds no such option just shows no
delete button.

This is also the limit of any capability table, including the ones further down. A table
tells you what a model can do. To find out which models your users actually hold, you need
the [AAGUID](https://www.corbado.com/glossary/aaguid) from your own ceremonies. Section 9 comes back to that.

Here is such a response, taken from the metadata record of a YubiKey 5 with NFC on firmware
5.8. We left out a few operational fields, but everything this article discusses is in
there:

```json
{
  "versions": ["U2F_V2", "FIDO_2_0", "FIDO_2_1_PRE", "FIDO_2_1", "FIDO_2_2"],
  "extensions": [
    "credProtect", "hmac-secret", "largeBlobKey", "credBlob",
    "minPinLength", "hmac-secret-mc", "thirdPartyPayment"
  ],
  "aaguid": "f4ce5fc057d346f5a736efb7d5bc63b5",
  "options": {
    "plat": false,
    "rk": true,
    "clientPin": false,
    "up": true,
    "pinUvAuthToken": true,
    "largeBlobs": true,
    "credMgmt": true,
    "setMinPINLength": true,
    "makeCredUvNotRqd": true,
    "alwaysUv": false
  },
  "maxMsgSize": 1536,
  "pinUvAuthProtocols": [2, 1],
  "maxCredentialCountInList": 8,
  "maxCredentialIdLength": 128,
  "transports": ["nfc", "usb", "smart-card"],
  "algorithms": [
    { "type": "public-key", "alg": -7 },
    { "type": "public-key", "alg": -8 },
    { "type": "public-key", "alg": -35 }
  ],
  "maxSerializedLargeBlobArray": 4096,
  "minPINLength": 4,
  "firmwareVersion": 329728,
  "maxCredBlobLength": 32,
  "remainingDiscoverableCredentials": 100
}
```

A handful of these values decide how your login behaves:

- `hmac-secret` for PRF
- `credProtect` for credential privacy
- `largeBlobs` and `credBlob` for extra bytes stored next to a credential
- `remainingDiscoverableCredentials` for how many passkeys still fit
- `maxCredentialCountInList` for how long your `allowCredentials` list may be
- `credMgmt` for whether a user can delete anything
- `alwaysUv` and `minPINLength` for policy enforced on the key
- `transports` for how the key can be reached

The rest of this article goes through them one by one. Vendor datasheets summarise the same
response, so we use the response itself.

### 2.2 FIDO Metadata Service publishes that response for 325 keys

The [FIDO Alliance Metadata Service](https://fidoalliance.org/metadata/) distributes a signed
JWT with one metadata statement per listed authenticator, keyed by
[AAGUID](https://www.corbado.com/glossary/aaguid). For CTAP2 authenticators that statement contains the
`authenticatorGetInfo` response the vendor submitted.

The snapshot used here is BLOB number 276, valid until 2026-09-01:

- 507 entries in total, covering FIDO2, U2F and UAF authenticators
- 337 entries contain an `authenticatorGetInfo` block
- 12 of those are platform authenticators (`plat: true`)
- 325 roaming authenticators remain, each with its own AAGUID

Of those 325, 257 carry a FIDO certification in their status report. 68 do not, and the
BLOB lists them anyway with `NOT_FIDO_CERTIFIED` as their latest status. All Ledger models
are in that group, together with several preview firmware entries. We kept them in, because
a key that ships and gets deployed affects your integration either way. That is also why
this article talks about "325 authenticators in the Metadata Service" and never about "325
certified keys". Dropping the 68 changes very little: `hmac-secret` goes from 94% to 95%,
credProtect from 76% to 79%, CTAP 2.1 from 59% to 61%.

One product family often occupies many AAGUIDs. Yubico, Feitian and others assign a separate
AAGUID per firmware generation, per FIPS or CCN variant and per consumer or enterprise
profile. Search the BLOB for "YubiKey 5" and "NFC" and you get 24 records, from firmware
5.4.2 up to 5.8.0. That is why the tables in this article collapse a product family into one row
and use the record with the highest `authenticatorVersion`, and why the percentages below
count records rather than products sold.

### 2.3 What this data cannot tell you

Three limitations apply to every row in the tables below:

- **It is a snapshot from submission.** A key that gained `largeBlob` support in a later
  firmware keeps its old capability set until the vendor submits a new record. The
  Nitrokey 3 entry reports `largeBlobs: false`, which describes the firmware it was
  submitted with, not the one shipping today.
- **A missing field is not a missing feature.** Older records were captured with tooling that
  left out fields such as `transports` or `remainingDiscoverableCredentials`. We mark those
  as "n/r" for not reported, never as "no".
- **Keys that were never submitted are not in the BLOB.** Parts of the Token2 range are
  missing or show up only with an older model. That says nothing about their quality.

Section 6 collects the models whose record is too thin for the main table.

## 3. How to read the options map

### 3.1 Missing, false and true mean three different things

The CTAP `options` map has three states:

- **Option missing:** the authenticator does not support the feature
- **Option set to `false`:** depends on the option, see below
- **Option set to `true`:** supported and active

The middle state causes most of the confusion, because CTAP uses it for two different
things.

**Configuration options** describe something the user sets up, so `false` means "supported,
but not configured yet". This covers `clientPin`, `uv`, `bioEnroll`, `uvBioEnroll`, `ep` and
`alwaysUv`. A boolean that means "supported, but switched off" is unusual, so most readers
treat `false` as a plain no. The data then becomes wrong in one specific direction: every
feature that a user configures looks unsupported across the whole field.

**Support-only options** have no configuration state, so `false` means exactly what it looks
like: not supported. This covers `rk`, `credMgmt`, `largeBlobs`, `pinUvAuthToken`,
`setMinPINLength` and `makeCredUvNotRqd`. It stays rare in the data, because a vendor that
does not support a feature usually leaves the option out: 4 records report `rk: false`, 4
report `credMgmt: false` and 7 report `largeBlobs: false`. We count those as no throughout
this article.

So for `clientPin`, `uv`, `bioEnroll`, `ep` and `alwaysUv`, `false` means "can, but has not".
Everywhere else it means "cannot".

### 3.2 Example: YubiKey Bio reports bioEnroll false

Every YubiKey Bio entry in the metadata carries `bioEnroll: false` together with `uv: false`
and `uvModality: 2`. Taken at face value that says a fingerprint key has no fingerprint.

The sensor exists, `uvModality: 2` identifies it as an internal fingerprint sensor and the
sample used for the submission had no fingerprint enrolled. The same pattern appears on
`clientPin: false`, which every YubiKey record carries and which means "PIN supported, none set
yet".

Across the 325 keys, 45 report a `bioEnroll` option and only 9 report it as `true`. The
useful number here is 45.

## 4. The capabilities one by one

### 4.1 CTAP version

`versions` lists the protocol versions a key speaks. Almost every key speaks CTAP 2.0 (322 of
325). CTAP 2.1 brought credential management, `largeBlob`, `minPinLength` and `alwaysUv`, and
only 193 keys (59%) declare it. Another 170 declare `FIDO_2_1_PRE`, the pre-standard draft
with a smaller command set and different command numbers, so it should not be counted as
CTAP 2.1.

CTAP 2.2 has started to arrive. Nine keys declare `FIDO_2_2` and 20 declare a `FIDO_2_3`
string. All of them are Yubico firmware 5.8 records plus one Feitian biometric card, and the
same records are the only ones with the newer `hmac-secret-mc` and `thirdPartyPayment`
extensions.

### 4.2 hmac-secret and WebAuthn PRF

[WebAuthn PRF](https://www.corbado.com/blog/passkeys-prf-webauthn) lets an application derive a stable symmetric key
from a passkey, which is the basis for end-to-end encrypted products. On a roaming
authenticator, PRF is served by the CTAP `hmac-secret` extension.

**306 of 325 keys (94%) support `hmac-secret`**, including keys submitted years ago. If your
feature needs PRF from a security key, the hardware will rarely be what holds you back.
Browser and platform support usually will.

One restriction sits underneath that number. Plain `hmac-secret` only produces a result
during authentication, so a registration ceremony cannot hand you the key material.
`hmac-secret-mc` lifts that restriction, and 20 of 325 keys support it: current Yubico
firmware and one Feitian card. An enrollment flow that expects a PRF output at registration
time therefore works on roughly 6% of the listed authenticators today.

### 4.3 credProtect

`credProtect` sets a policy per credential for whether the credential may be used without
user verification. It is the mechanism that keeps a discoverable credential from revealing
that it exists. We described the browser behaviour and Chrome's escalation rules in
[WebAuthn credProtect on security keys](https://www.corbado.com/blog/webauthn-credprotect-security-keys).

247 of 325 keys (76%) support it, and the gap splits cleanly along protocol generations:
none of the 78 records without `credProtect` declares CTAP 2.1. They are all CTAP 2.0-era
entries, among them the Ledger models, SoloKeys and OpenSK, the older Security Key by Yubico
and several enterprise smartcards. The reverse does not hold, so do not use CTAP 2.0 as a
shortcut. The Google Titan v2 record stops at CTAP 2.0 and lists `credProtect` anyway, which
is why the table in section 6 marks it yes.

### 4.4 largeBlob and credBlob

Both store additional bytes next to a credential, but they are not interchangeable:

- **`credBlob`** puts a small blob inside the credential, limited by `maxCredBlobLength`. 150
  keys (46%) support it and 122 of them cap it at exactly 32 bytes, which is enough for an
  identifier and not enough for a certificate.
- **`largeBlob`** stores a compressed per-credential blob in a separate array, sized by
  `maxSerializedLargeBlobArray`. 134 keys (41%) declare the `largeBlobKey` extension and 133
  report `largeBlobs` as true. The array holds 4096 bytes on current Yubico firmware, 2048
  bytes on Token2 PIN Plus and Thetis Pro and 1024 bytes on Feitian BioPass Pro and Hyper
  FIDO Pro.

An SSH certificate that fits into the 4096 bytes on a YubiKey will not fit into a 1024-byte
array.

### 4.5 Capacity for discoverable credentials

A [discoverable credential](https://www.corbado.com/glossary/discoverable-credential) makes usernameless login
possible and occupies a physical slot on the key. `remainingDiscoverableCredentials` reports
how many slots were free on the submitted sample, which for a factory-reset key is the total
number.

150 keys report the field. The median is 100, the range goes from 6 to 662, so the largest
key holds 110 times as much as the smallest:

```mermaid
xychart-beta
    title "Reported discoverable credential slots per model"
    x-axis ["OneSpan FX1-C", "OneSpan FX7-C", "Swissbit iShield 2", "GoldKey", "Thetis Pro", "Feitian BioPass Pro", "YubiKey 5", "Token2 PIN Plus", "HID Crescendo V3", "YubiKey Bio"]
    y-axis "Reported slots" 0 --> 700
    bar [662, 368, 300, 256, 200, 128, 100, 50, 30, 25]
```

The same values as a table:

| Key | Reported slots |
|---|---|
| OneSpan DIGIPASS FX1-C | 662 |
| OneSpan DIGIPASS FX7-C | 368 |
| Swissbit iShield Key 2 | 300 |
| GoldKey Security Token | 256 |
| Thetis Pro FIDO2 | 200 |
| Feitian BioPass FIDO2 Pro, Hyper FIDO Pro | 128 |
| YubiKey 5 (firmware 5.7 and 5.8) | 100 |
| Token2 PIN Plus, Taglio CTAP2.1 BIO | 50 |
| HID Crescendo Key V3 | 30 |
| YubiKey Bio (records before 5.8) | 25 |

Non-discoverable credentials work differently and are practically unlimited, because the
credential is wrapped into the credential ID and nothing stays on the key. For a workforce
that registers on dozens of relying parties, that design choice usually decides whether
users run into a limit at all. We compare both models in
[resident keys and discoverable credentials](https://www.corbado.com/blog/webauthn-resident-key-discoverable-credentials-passkeys).

### 4.6 Credential management on the key

`credMgmt` allows a user to list and delete the credentials stored on a key, which is the
only way to free a slot without a factory reset. `credMgmt` is a support-only option, so
the count that matters is the 193 keys (59%) that report it as `true`. Four more carry
`credMgmt: false`, which per section 3.1 means no support. Another 164 expose only the
pre-standard `credentialMgmtPreview`, which most tools handle as well, under different
command numbers.

Where credential management is missing, "my key is full" has exactly one answer, and that
answer deletes every credential on the key. Our guide on
[deleting a passkey from a YubiKey](https://www.corbado.com/blog/delete-passkey-from-yubikey) shows the supported
path.

### 4.7 Bio enrollment

46 keys expose a bio option (`bioEnroll` or `uvBioEnroll`), which is 14% of the field. Read
with the rule from section 3.1, that is how many keys have a fingerprint sensor.
`uvModality: 2` marks an internal fingerprint sensor and appears on almost all of them.

There are two variants. `bioEnroll` means enrollment happens on the key itself.
`uvBioEnroll` means that enrollment is allowed through a `pinUvAuthToken`, which is what most
current biometric keys from Feitian, Thales, OneSpan and ACS report.

### 4.8 PIN policy, enterprise attestation and always-UV

These are the enterprise controls, and they are the thinnest part of the data:

- **`setMinPINLength`** (enforce a minimum PIN length): 170 keys, 164 of them reporting true.
  The default `minPINLength` is 4 on 141 keys, 6 on 33 and 8 on 11.
- **`alwaysUv`** (require user verification for every operation): present on 170 keys, true
  on only 51, because this is a setting rather than a capability.
- **`ep`** (enterprise attestation, which returns a uniquely identifying attestation to a
  permitted relying party): present on 51 keys, true on 14. An administrator switches it on
  per key, so `false` is the expected value on a factory sample.
- **`pinUvAuthProtocols`**: 193 keys support both protocol versions 1 and 2, 96 support only
  version 1, 6 support only version 2 and 30 report none.

### 4.9 Transports

244 keys report a `transports` array: USB on 198, NFC on 132, smart-card on 20, BLE on 17 and
Lightning on 8. Yubico firmware 5.8 records now also list `smart-card`, which is how the key
presents itself to iOS and Android through the built-in reader path.

For [smartcard form factors](https://www.corbado.com/blog/best-fido2-smartcards) the transport list decides the
rollout, because an NFC-only card cannot be enrolled on a desktop without a reader.

## 5. Comparison table: current CTAP 2.1 security keys

### 5.1 Which keys are in the table and why

193 records declare CTAP 2.1, but they are not 193 products. They carry 168 distinct product
names, and those collapse much further once firmware, FIPS, CCN and profile variants are
merged into the model you can actually order. The 16 rows below are that set, picked by three
rules:

- **One row per product family**, using the entry with the highest `authenticatorVersion`.
  Without that step Yubico alone would fill a third of the table.
- **CTAP 2.1 declared**, because those are the records that carry the blob, capacity and
  policy fields at all. The CTAP 2.0 models that are still widely deployed get their own
  table in section 6.
- **Available to buy today**, dropping preview, RC and discontinued records.

"n/r" means the field is not reported, which is not the same as unsupported.

| Key | CTAP | hmac-secret (PRF) | credProtect | largeBlob | credBlob | Discoverable slots | maxCredentialCountInList | Transports |
|---|---|---|---|---|---|---|---|---|
| YubiKey 5 with NFC (fw 5.8) | 2.0 / 2.1 / 2.2 | yes | yes | yes, 4096 B | yes, 32 B | 100 | 8 | USB, NFC, smart-card |
| Security Key NFC by Yubico (fw 5.8) | 2.0 / 2.1 / 2.2 | yes | yes | yes, 4096 B | yes, 32 B | 100 | 8 | USB, NFC, smart-card |
| YubiKey Bio, FIDO Edition (fw 5.8) | 2.0 / 2.1 | yes | yes | yes, 4096 B | yes, 32 B | 100 | 8 | USB, smart-card |
| Feitian ePass FIDO2-NFC (CTAP 2.1) | 2.0 / 2.1 | yes | yes | yes, size n/r | yes, size n/r | n/r | 6 | USB, NFC |
| Feitian BioPass FIDO2 Pro | 2.0 / 2.1 | yes | yes | yes, 1024 B | yes, 32 B | 128 | 10 | USB |
| Token2 PIN Plus Series | 2.0 / 2.1 | yes | yes | yes, 2048 B | yes, 32 B | 50 | 8 | USB, NFC |
| Thetis Pro FIDO2 | 2.0 / 2.1 | yes | yes | yes, 2048 B | yes, 32 B | 200 | 8 | USB, NFC |
| Swissbit iShield Key 2 | 2.0 / 2.1 | yes | yes | no | yes, 32 B | 300 | n/r | USB, NFC |
| OneSpan DIGIPASS FX7 | 2.0 / 2.1 | yes | yes | yes, 2008 B | yes, 64 B | 139 | 8 | USB |
| Hyper FIDO Pro (CTAP 2.1) | 2.0 / 2.1 | yes | yes | yes, 1024 B | yes, 32 B | 128 | 10 | USB |
| GoTrust Idem Key mini | 2.0 / 2.1 | yes | yes | yes, 4096 B | yes, 32 B | 50 | 8 | USB |
| Nitrokey 3 AM | 2.0 / 2.1 | yes | yes | no | no | n/r | 10 | USB |
| HID Crescendo Key V3 | 2.0 / 2.1 | yes | yes | no | no | 30 | 1 | USB, NFC |
| ACS PocketKey+ Bio | 2.0 / 2.1 | yes | yes | no | yes, 64 B | n/r | 5 | USB, NFC |
| Taglio CTAP2.1 BIO (card) | 2.0 / 2.1 | yes | yes | yes, 2048 B | yes, 128 B | 50 | n/r | NFC |
| Ensurity AUTH BioPro | 2.1 | yes | yes | no | no | n/r | 10 | USB |

The Yubico rows also declare a `FIDO_2_3` version string next to CTAP 2.2. That is a draft
label and not a published standard version, so the table lists the standardised versions
only.

### 5.2 Reading across the rows

The metadata gives you these values one AAGUID at a time. Side by side, four things show up
that you cannot see in a single record:

- **PRF and credProtect are a solved problem on current hardware.** Every row supports
  `hmac-secret` and `credProtect`. The differences start at `largeBlob`, which five of these
  models do not report at all, among them the Swissbit iShield Key 2 and the HID Crescendo
  Key V3.
- **"Supports largeBlob" covers very different sizes.** Among CTAP 2.1 keys the reported
  capacity is 1024, 1978, 2008, 2048, 4056 or 4096 bytes. What fits into 4096 bytes on a
  YubiKey runs out of room on a Feitian BioPass at 1024. `credBlob` spreads the same way,
  from 32 to 256 bytes.
- **Capacity has little to do with price or brand.** Inside this table it ranges from 30 to
  300 slots, and the two keys at the top, Swissbit and Thetis, are rarely the ones a
  workforce rollout standardises on.
- **One row needs different client behaviour.** The HID Crescendo Key V3 reports a
  `maxCredentialCountInList` of 1, so a client has to send one request per credential, see
  section 7.3.

The Nitrokey 3 row needs a warning in the other direction. Its record is the clearest case of a
metadata snapshot lagging behind shipped firmware, so its blob columns mean "not in the
submitted record" and not "cannot do this".

## 6. Keys whose record stops at CTAP 2.0

These models are widely deployed, but their metadata entry predates the CTAP 2.1 reporting
fields. Each row describes the record the vendor submitted, which can differ from what the
key in your drawer does today. Check them with the key in hand, see section 8, before you
design against them.

| Key | CTAP declared | hmac-secret | credProtect | Notes from the record |
|---|---|---|---|---|
| Google Titan Security Key v2 | 2.0 | yes | yes | No CTAP 2.1 options, no transports and no capacity reported |
| Token2 FIDO2 Security Key | 2.0 / 2.1-PRE | yes | yes | Only `credentialMgmtPreview`, newer Token2 models are in the table above |
| eWBM eFA320 | 2.0 / 2.1-PRE | yes | yes | Reports `uv: true`, preview credential management only |
| Kensington VeriMark Guard | 2.0 / 2.1-PRE | yes | yes | Fingerprint key, reports `uv: true`, list cap 20 |
| uTrust FIDO2 Security Key | 2.0 | yes | no | Minimal record, no options beyond PIN and RK |
| SafeNet eToken Fusion | 2.0 | yes | no | Minimal record, newer eToken Fusion variants report much more |
| SoloKeys Solo, OpenSK | 2.0 | yes | no | Open-source firmware, records are old |
| Ledger Flex, Ledger Stax | 2.0 | yes | no | Not FIDO certified, `rk: false` on both records |

`rk: false` on both Ledger models means the device cannot store a discoverable credential at
all, so it will never serve a usernameless login. That is a real limitation, not a reporting
gap.

## 7. The picture across the whole field

### 7.1 Extension support across 325 authenticators

```mermaid
xychart-beta
    title "Share of the 325 authenticators supporting each capability"
    x-axis ["hmac-secret", "CTAP 2.1", "credProtect", "minPinLength", "credBlob", "largeBlobKey", "hmac-secret-mc", "CTAP 2.2"]
    y-axis "Share of keys in %" 0 --> 100
    bar [94, 59, 76, 47, 46, 41, 6, 3]
```

The same numbers with the absolute counts:

| Capability | Keys | Share |
|---|---|---|
| `hmac-secret` (PRF) | 306 | 94% |
| `credProtect` | 247 | 76% |
| `minPinLength` | 153 | 47% |
| `credBlob` | 150 | 46% |
| `largeBlobKey` | 134 | 41% |
| `hmac-secret-mc` | 20 | 6% |
| `thirdPartyPayment` | 20 | 6% |
| CTAP 2.0 declared | 322 | 99% |
| CTAP 2.1 declared | 193 | 59% |
| CTAP 2.2 declared | 9 | 3% |
| U2F fallback | 243 | 75% |

Teams usually assume PRF is the exotic capability and that credential storage comes with
every key. It is the other way round. PRF is the safest thing on this list to build on,
while the blob and management features that make an enterprise rollout comfortable sit below
50%.

Signature algorithms show the same concentration: ES256 (COSE `-7`) on 231 keys, EdDSA
(`-8`) on 93, ES384 (`-35`) on 78 and RS256 (`-257`) on 6. Leave `-7` out of
`pubKeyCredParams` and you lock out most of the field.

### 7.2 Capacity varies the most

Only 150 of 325 keys report their capacity at all. For the other 175 the metadata will not
carry a rollout with discoverable credentials and you have to measure. Among the keys that
do report, the values cluster tightly around 100:

```mermaid
xychart-beta
    title "Reported discoverable credential slots, 150 keys that state a number"
    x-axis ["6-25", "26-50", "51-100", "101-200", "201-662"]
    y-axis "Number of keys" 0 --> 90
    bar [8, 29, 80, 21, 12]
```

69 of those 150 report exactly 100 slots, which has become the de facto standard for the
mid-range. 8 keys stop below 26 slots and 12 go past 200, so a rollout sized on the median
breaks at both ends.

### 7.3 maxCredentialCountInList and long allowCredentials lists

`maxCredentialCountInList` limits how many entries the authenticator accepts in one
`allowCredentials` list. 222 keys report it and the values sit close together, with one
group of outliers that causes the trouble:

```mermaid
xychart-beta
    title "maxCredentialCountInList, 222 keys that report a limit"
    x-axis ["1", "5", "6", "8", "10", "16", "20", "35", "other"]
    y-axis "Number of keys" 0 --> 120
    bar [9, 3, 21, 108, 26, 23, 18, 9, 5]
```

The nine keys with a limit of 1 are all HID Crescendo variants. 8 is the value for roughly
half of the field.

If a relying party sends more credential IDs than the limit allows, the client cuts the list
into chunks of that size and sends one `authenticatorGetAssertion` per chunk. Nothing gets
dropped, it simply takes several requests. Over USB nobody notices. Over NFC each request is
another tap on the reader, so on a key with a limit of 1 a list of 20 credentials means 20
taps before the user sees any result.

Only a shorter list fixes this. Send an empty `allowCredentials` and rely on discoverable
credentials, or reduce the list on the server to the credentials that plausibly belong to this
user and this transport.

## 8. How to check a key yourself

The metadata tells you what a vendor submitted. Only the key in your hand tells you what is
installed on it. Two commands are enough for that.

Start with [`fido2-token`](https://developers.yubico.com/libfido2/Manuals/fido2-token.html)
from [libfido2](https://developers.yubico.com/libfido2/), on macOS via `brew install
libfido2`. It speaks plain CTAP over HID and therefore reads any FIDO2 key, whoever made it.
First list what is connected. On macOS the device path is an `ioreg://` handle, on Linux it
is a `/dev/hidraw*` node:

```bash
$ fido2-token -L
ioreg://4296123542: vendor=0x1050, product=0x0407 (Yubico YubiKey OTP+FIDO+CCID)
```

Then read the `getInfo` response from that path. This is a YubiKey 5 NFC that has been in
daily use for a while:

```bash
$ fido2-token -I ioreg://4296123542
proto: 0x02
major: 0x05
minor: 0x04
build: 0x03
caps: 0x05 (wink, cbor, msg)
version strings: U2F_V2, FIDO_2_0, FIDO_2_1_PRE
extension strings: credProtect, hmac-secret
transport strings: nfc, usb
algorithms: es256 (public-key), eddsa (public-key)
aaguid: 2fc0579f811347eab116bb5a8db9202a
options: rk, up, noplat, clientPin, credentialMgmtPreview
fwversion: 0x50403
maxmsgsiz: 1200
maxcredcntlst: 8
maxcredlen: 128
maxcredblob: 0
maxlargeblob: 0
minpinlen: 4
pin protocols: 2, 1
pin retries: 8
```

On a YubiKey, [`ykman`](https://developers.yubico.com/yubikey-manager/) adds the state a
user cares about:

```bash
$ ykman fido info
AAGUID:             2fc0579f-8113-47ea-b116-bb5a8db9202a
PIN:                8 attempt(s) remaining
Minimum PIN length: 4
```

Note that `ykman` finds Yubico hardware only, because it filters the USB device list on
Yubico's vendor ID. For a Feitian, Token2 or HID key you need `fido2-token`.

### 8.1 Comparing that output against the metadata

Look up the AAGUID `2fc0579f-8113-47ea-b116-bb5a8db9202a` in the BLOB and you get "YubiKey 5
Series with NFC". Line by line the two agree: the same two extensions, the same
`maxCredentialCountInList` of 8, the same transports, the same algorithms. Two fields differ,
and both differences are the ones this article warned about:

- **`firmwareVersion`.** The record says `328706`, the key says `0x50403`. Yubico encodes the
  version as major, minor and patch bytes, so that is 5.4.2 in the metadata against 5.4.3 in
  the hand. The same encoding makes `329476` firmware 5.7.4 and `329728` firmware 5.8.0.
- **`clientPin`.** The record says `false`, this key lists `clientPin` in its options and
  reports 8 PIN attempts remaining. That is exactly the section 3.1 rule at work: the
  submitted sample had no PIN set, this one does.

The output also shows where the capability tables run out. This key declares `FIDO_2_1_PRE`
instead of `FIDO_2_1`, so it reports `maxcredblob: 0`, `maxlargeblob: 0` and no capacity at
all. Nothing is broken here. It is a working CTAP 2.0-era key, and it belongs to the 41% that
contribute nothing to the `largeBlob` and `remainingDiscoverableCredentials` numbers above.

If you cannot plug the key in, the AAGUID from an
[attestation statement](https://www.corbado.com/glossary/attestation) is enough to find its metadata entry. That is
also how a relying party identifies hardware in production.

## 9. What this means for Relying Parties

Four decisions follow from this data alone, without any vendor preference:

- **Rely on PRF, plan around largeBlob.** At 94% against 41%, `hmac-secret` is a safe
  dependency while `largeBlob` needs a graceful fallback. If your enrollment flow needs a PRF
  result at registration time, treat `hmac-secret-mc` as a small minority and keep a path
  that derives the key during authentication.
- **Keep `allowCredentials` short.** The limit is 8 on half of the field and 1 on some
  enterprise cards, so a long list becomes a latency problem as soon as the wrong hardware
  shows up.
- **Do not treat capacity as unlimited.** 30 slots and 662 slots are both normal values, and
  a workforce rollout that assumes the upper end produces support tickets that read as "my
  key stopped working".
- **Choose `userVerification` deliberately.** `credProtect` behaves differently across
  browsers, as we documented in the
  [credProtect article](https://www.corbado.com/blog/webauthn-credprotect-security-keys). CTAP 2.1 keys let you
  enforce policy on the key itself with `alwaysUv` and `minPinLength`, but only the 59% that
  declare CTAP 2.1.

A datasheet answers none of these questions and the metadata answers most of them. The last
one it cannot answer is which AAGUIDs actually show up in your traffic and which of them
fail. You only get that from production.

## 10. How Corbado can help

A capability table tells you what the hardware can do. It says nothing about which hardware
your users hold or which of those models fails in your login. The AAGUID data from your own
ceremonies answers both, and that is what [Corbado Observe](https://www.corbado.com/observe), our authentication
observability layer, collects.

[Video: Authenticator inventory](https://www.corbado.com/videos/features/authenticator-inventory.mp4) ([See Authenticator Inventory in Corbado Observe](https://www.corbado.com/observe/authenticator-inventory))

- **See the authenticator behind every ceremony.** The inventory resolves the AAGUID to a
  model, so "our workforce uses YubiKeys" turns into a distribution with the long tail of
  Feitian, HID and Token2 keys that procurement never mentioned.
- **Find capability mismatches as an error pattern.** WebAuthn failures are attributed by
  authenticator, OS and browser, so a `largeBlob` or `credProtect` assumption that only
  breaks on one model shows up as a cluster instead of as scattered noise.
- **Separate device-bound from synced credentials.** The split is reported per segment,
  which is usually the number a regulated environment asks for.

If you roll out security keys next to platform passkeys,
[hardware passkey observability](https://www.corbado.com/blog/hardware-passkey-adoption-observability) describes how
the two populations differ in practice.

## 11. Conclusion

None of this data was ever hidden. It sits in a signed file that the FIDO Alliance updates
every month, one `authenticatorGetInfo` response per listed key, and it answers most of the
questions that teams still answer by guessing or by buying a key and trying it out.

PRF sits at 94%, higher than most teams expect. CTAP 2.1 stops at 59%, credential blobs stay
below half and reported capacity spans two orders of magnitude. Build on the 94% and degrade
gracefully around the rest.

Two caveats apply to every row above. The metadata is a snapshot from the moment of
submission, so firmware moves ahead of it, and roughly half of the records leave out the
capacity and transport fields. Read a key in your hand with `fido2-token -I` before you
commit a design to it, and measure which AAGUIDs your users present before you commit a
rollout.

## Frequently Asked Questions

### Which FIDO2 security keys support the WebAuthn PRF extension?

PRF on a security key needs the CTAP `hmac-secret` extension. 306 of the 325
roaming authenticators in the FIDO Metadata Service support it, which is 94% and makes it the
most widely implemented CTAP extension. The newer `hmac-secret-mc`, which lets a key return a
PRF result during registration, is much rarer at 20 of 325 keys.

### How many passkeys can a FIDO2 security key store?

The range is wide. Of the 150 keys that report
`remainingDiscoverableCredentials` in their CTAP `getInfo` response, the median is 100. The
OneSpan DIGIPASS FX1-C reports 662, the Swissbit iShield Key 2 reports 300 and the HID
Crescendo Key V3 reports 30. Non-discoverable credentials are practically unlimited on almost
every key, because nothing is stored on the device.

### What does bioEnroll false mean in a CTAP getInfo response?

It means the key has a fingerprint sensor, but no fingerprint has been enrolled yet. In CTAP
an option missing from the `options` map is unsupported and `true` means supported and
active. `false` depends on the option: for the ones a user sets up, such as `bioEnroll`,
`clientPin`, `uv`, `ep` and `alwaysUv`, it means supported but not configured, while for
support-only options such as `rk`, `credMgmt` and `largeBlobs` it means not supported.
Reading `bioEnroll: false` as no support is the most common mistake when interpreting
`getInfo`, because it makes every YubiKey Bio look like it has no biometrics.

### Why does a security key not accept my whole allowCredentials list at once?

Every key states a limit in its `getInfo` response, `maxCredentialCountInList`, for how many
credential IDs it accepts in a single request. The most common limit is 8. If a login sends
more IDs than that, the browser has to cut the list into chunks of that size and ask the key
once per chunk. Over USB nobody notices. Over NFC every extra request is another tap on the
reader, and nine keys in the metadata, all HID Crescendo variants, have a limit of 1, so a
list of 20 credentials becomes 20 taps. Keep the list short, or send an empty
`allowCredentials` and let the key use its discoverable credentials.

### Where can I look up the capabilities of a specific security key?

The [FIDO Alliance Metadata Service](https://fidoalliance.org/metadata/) publishes a signed
BLOB with the submitted `authenticatorGetInfo` response for every FIDO2 authenticator it
lists, keyed by AAGUID. For a key you can plug in,
[`fido2-token -I`](https://developers.yubico.com/libfido2/Manuals/fido2-token.html) from
libfido2 prints the same response from the firmware that is actually installed, for any
vendor. `ykman fido info` does the same, but only for YubiKeys.
