Challenge-response
The other OTP application configurations (Yubico OTP, OATH HOTP, and static password) require the user to activate the configured slot (by touching the YubiKey or scanning it with an NFC reader) in order to generate and submit the password from the YubiKey to a host device. Challenge-response, on the other hand, begins with a “challenge” that a host sends to the YubiKey. The YubiKey receives the challenge (as a byte array) and “responds” by encrypting or digesting (hashing) the challenge with a stored secret key and sending the response code back to the host for authentication.
Challenge-response is flexible. It can be used in single and multi-factor authentication for logging into applications or devices, and validation can take place on a host device itself or on a validation server on an internal or external network. The SDK supports all of these scenarios.
To implement challenge-response authentication with a .NET application, the following must occur:
A slot on the YubiKey must be configured with a secret key and encryption/hashing algorithm.
The application must be able to send challenges to and receive responses from a YubiKey.
A copy of the secret key must be shared with the validating party.
The validating party must be able to validate responses and pass the result back to the application.
Note
All YubiKey-host communication for challenge-response is done via the HID communication protocol. Therefore, challenge-response authentication will only work when a YubiKey is physically plugged into a host over USB or Lightning. Challenges and responses cannot be communicated wirelessly with NFC.
Supported challenge-response algorithms
The .NET SDK and the YubiKey support the following encryption and hashing algorithms for challenge-response:
Yubico OTP (encryption)
HMAC-SHA1 as defined in RFC2104 (hashing)
For Yubico OTP challenge-response, the key will receive a 6-byte challenge. The YubiKey will then create a 16-byte string by concatenating the challenge with 10 bytes of unique device fields. For Yubico OTP challenge-response, these 10 bytes of additional data are not important. They are merely added as padding so that the challenge may then be encrypted with a 16-byte key using the AES encryption algorithm (AES requires that data be encrypted in blocks of the same size as the encryption key). The resulting Yubico OTP becomes the response code.
For HMAC-SHA1 challenge-response, the key will receive a challenge of up to 64 bytes in size, which will be digested ( hashed) with a 20-byte secret key, resulting in a 6-10 digit HOTP as the response code.
Note
Hashing/digesting is a one-way operation, meaning that once a block of data is hashed, it cannot be converted back into its original form. Encryption, on the other hand, is a two-way operation. When a block of data is encrypted, it can be decrypted back into its original form at any time. This is an important distinction because the validating party will have to respond differently to Yubico OTP responses (encrypted) and HMAC-SHA1 responses (hashed). For Yubico OTP, the validating party will have to decrypt the response and compare the result with the original challenge. For HMAC-SHA1, the validating party will have to perform the same hashing operation with the original challenge and compare the result to the response received from the YubiKey.
Challenge initiation and authentication
The challenge-response process works as follows:
The YubiKey is connected to the host.
The application on the host sends a challenge to a specific slot of the YubiKey via the SDK.
The YubiKey receives the challenge and encrypts/digests it with the secret key and encryption/hashing algorithm that the slot was configured with.
The YubiKey sends the response back to the host, and the application receives it as a string object containing numeric digits, a byte array, or a single integer (as determined by the SDK).
The application sends the response to the validating party. For Yubico OTP challenge-response, the response must be decrypted using the YubiKey’s unique secret key. For HMAC-SHA1 challenge-response, the validating party must digest the challenge with the secret key using the same HMAC-SHA1 algorithm.
For Yubico OTP, if the decrypted response matches the original challenge that was sent to the YubiKey, authentication was successful, and the user is logged in. (For Yubico OTP challenge-response, the 6-byte challenge must match the first 6 bytes of the decrypted response—the other bytes are ignored.) For HMAC-SHA1, if the response matches the validating party's digested challenge, authentication was successful, and the user is logged in.
Note
For the authentication process to succeed, the size of the challenge must align with the algorithm that the YubiKey was configured with. Similarly, the validating party must decrypt the response using the same algorithm that the challenge was encrypted with.
SDK functionality
The SDK’s challenge-response functionality centers around the following two methods:
ConfigureChallengeResponse()
allows you to configure an OTP application slot on a YubiKey to receive a challenge
from a host and process it based on a specific algorithm and secret key. CalculateChallengeResponse()
allows a host
to send a challenge to a YubiKey and then receive the response from the YubiKey.
ConfigureChallengeResponse()
When calling ConfigureChallengeResponse()
, you must set the secret key for the slot, which can be generated randomly
via GenerateKey()
or set explicitly
with UseKey().
If you choose to generate a key, that key must be shared with the validating party before being cleared from memory.
Secrets cannot be extracted from the YubiKey once configured.
You must also set the algorithm that will be used to respond to challenges by calling
either UseHmacSha1()
or UseYubiOtp(). For example, if you
call UseHmacSha1()
, the YubiKey will digest challenges it receives with the secret key via the HMAC-SHA1 algorithm.
Note
It’s important that the size of your secret key matches the size that is expected for the algorithm you
chose (16 bytes for Yubico OTP
and 20 bytes for HMAC-SHA1). For
example, if you call UseYubiOtp()
, the key that you set with UseKey()
must be 16 bytes long. Otherwise, the
YubiKey will not be able to respond to a challenge correctly. The SDK will throw an exception if the key length is
incorrect for the chosen configuration.
The ConfigureChallengeResponse
class also provides optional methods for requiring users to touch the YubiKey to
initiate the challenge-response
operation (UseButton())
or enabling the key to process HMAC-SHA1 challenges of less than 64
bytes (UseSmallChallenge()).
Note
UseSmallChallenge()
is included for compatibility with legacy systems whose implementations break data sets into
multiple blocks, which often results in the last element being smaller than 64 bytes.
For a full list of the methods in the ConfigureChallengeResponse
class, please see
the API documentation.
For an example of how to use ConfigureChallengeResponse()
, please
see How to program a slot with a challenge-response credential.
CalculateChallengeResponse()
In order for a host to send a challenge to a YubiKey and receive a response, an application on the host must
call CalculateChallengeResponse()
. With this method, you can:
send the challenge to the YubiKey as a byte array with UseChallenge().
send a message to the user to notify them to touch the YubiKey to initiate the challenge-response operation with UseTouchNotifier(). This is only needed if the YubiKey slot was configured to require the button touch with
UseButton()
.receive the response from the YubiKey. The response can be received as a string object of numeric digits via GetCode(), as a byte array via GetDataBytes() (the only response type that is compatible with Yubico OTPs), or as a single 32-bit integer via GetDataInt().
Note
The size of the challenge sent to the YubiKey with UseChallenge()
must align with the slot's configuration. If the
slot is configured to perform Yubico OTP, the challenge must
be 6 bytes long. If the slot
is
configured for HMAC-SHA1, the challenge must
be 64 bytes long. However, if
the
slot has been configured with UseSmallChallenge()
, an HMAC-SHA1 challenge smaller than 64 bytes is acceptable. The
SDK will throw an exception if the challenge size does not match the YubiKey slot's configuration.
Alternatively, the application can send a TOTP challenge to the YubiKey
with UseTotp(). The time period of the TOTP
challenge (i.e. how long a TOTP is valid for) can be set
via WithPeriod() (the
default period is 30 seconds). TOTP challenges can only be used with keys configured for HMAC-SHA1 challenge-response.
With UseTotp()
, the application will send the current time as the challenge, and the YubiKey will digest it with the
stored secret key and the HMAC-SHA1 algorithm. The SDK will throw an exception if you call both UseTotp()
and UseChallenge()
.
For a full list of the methods in the CalculateChallengeResponse
class, please see
the API documentation.
For an example of how to use CalculateChallengeResponse()
, please
see How to calculate a response code for a challenge-response credential.