A one-time password (OTP) is a password that is only valid once. It can be used as an additional factor in multi-factor authentication and can be delivered in many ways, often via SMS. For example, you’d login with a username and password as usual, but then you’d be texted a time-sensitive one-time password to provide to the service before you’re authenticated.
Sometimes, OTP is used by itself. They are a great improvement over a simple password, owing to their use of (pseudo-) randomness, cryptographic hash functions and invulnerability to replay attacks.
In this post, I’m going to address only a single use of YubiKey, a hardware authentication device from Yubico; its one-time password implementation. I’ll (very) briefly address the following:
Yubico OTP
So, what does a Yubico one-time password look like? Here’s an example:
cccjgjgkhcbbirdrfdnlnghhfgrtnnlgedjlftrbdeut
The first part in bold is the public id of the YubiKey itself, and it never changes. This can be viewed as the “username”, and since it is embedded in the OTP it doesn’t need to be transmitted on its own. That’s nice. It’s one of the advantages of using an OTP.
The remaining characters make up the bits of the unique one-time password which is generated using the following information:
- Private ID (6 bytes)
- Usage Counter (2 bytes)
- Timestamp (3 bytes)
- Session Usage Counter (1 bytes)
- Random number (2 bytes)
- Checksum (2 bytes)
The hardware token acts like a keyboard and outputs the OTP as a 44-character string that is then verified by YubiCloud Yubico’s web service (or a custom authentication server for when users want their own auth server).
It’s really that simple: you place your cursor in a text box, touch the Yubikey, and, like magic, the one-time password character string is outputted from the device into the text box. All the identifying information and proof of ownership is transmitted in that mighty string. Of course, the server has to implement the functionality to then take this string and authenticate it, but that’s beyond the scope of this introductory post.
General YubiKey Questions
I had several questions concerning YubiKeys and their use. Here are some.
How is the OTP validated?
I understood that Yubico uses 128-bit AES symmetric keys to encrypt the OTP in the device and decrypt in the YubiCloud, but I didn’t understand how the verification server had access to the key. Well, it turns out that the symmetric key is loaded onto the token at the factory and is located in slot 1.
Interestingly, the factory-generated OTP credentials begin with the cc
modhex (modified hexadecimal) encoding scheme prefix. You can see an example of that above (note that the prefix starts the public id portion of the one-time password).
It is possible to delete this and/or create new credentials, and in a bit we’ll see an example of the latter. When doing so, the user-generated OTP credentials begin with the vv
prefix.
Either way, the authentication, either Yubicloud or another, will use the same AES symmetric key to decrypt the encrypted OTP and then verify the one-time password using the checksum.
The counters are used to guard against a replay attack. In other words, the OTP is immediately rejected if the counter values in the one-time password are less than the counter values on the authenticating server.
The image below is taken from the Yubico documentation:
Is there a command line tool?
Thankfully, yes. On Ubuntu systems, install the following tool:
$ sudo apt install -y yubikey-manager
From what I’ve read, the
yubikey-manager
package has since supplanted theyubikey-personalization-gui
package which contains the personalization tool, which has been (or will) be deprecated. In any case, the latter is no longer being actively developed, which is not a good sign.
If you’re like me and you reasonably expected the package to install a yubikey-manager
binary, it doesn’t. Instead, it installs the ykman
binary:
$ dpkg -L yubikey-manager
/.
/usr
/usr/bin
/usr/bin/ykman
[snipped for brevity]
$
$ ykman -v
YubiKey Manager (ykman) version: 3.1.1
Libraries:
libykpers 1.19.0
libusb 1.0.23
How can I interact with my YubiKey from the command line?
To list all connected YubiKeys:
$ ykman list --serials
10358814
To manage the connection modes:
$ ykman config mode [OPTIONS] MODE
To inspect the device:
$ ykman info
Device type: YubiKey 5 NFC
Serial number: 10358814
Firmware version: 5.1.2
Form factor: Keychain (USB-A)
Enabled USB interfaces: OTP+FIDO+CCID
NFC interface is enabled.
Applications USB NFC
OTP Enabled Enabled
FIDO U2F Enabled Enabled
OpenPGP Enabled Enabled
PIV Enabled Enabled
OATH Enabled Enabled
FIDO2 Enabled Enabled
A synonym would be:
$ ykman --device $(ykman list --serials) info
Check the status of the slots:
$ ykman otp info
Slot 1: programmed
Slot 2: empty
Visit the docs for all OTP commands and examples.
How can I read the information already programmed into the slots on my YubiKey?
You can’t.
How can I program the second slot?
To program the second slot for OTP, we want to use the yubiotp
subcommand. View its help menu:
$ ykman otp yubiotp -h
Usage: ykman otp yubiotp [OPTIONS] [1|2]
Program a Yubico OTP credential.
Options:
-P, --public-id MODHEX Public identifier prefix.
-p, --private-id HEX 6 byte private identifier.
-k, --key HEX 16 byte secret key.
--no-enter Don't send an Enter keystroke after emitting the OTP.
-S, --serial-public-id Use YubiKey serial number as public ID. Conflicts with --public-id.
-g, --generate-private-id Generate a random private ID. Conflicts with --private-id.
-G, --generate-key Generate a random secret key. Conflicts with --key.
-u, --upload Upload credential to YubiCloud (opens in browser). Conflicts with --force.
-f, --force Confirm the action without prompting.
-h, --help Show this message and exit.
To make it as easy as possible for this demo, we’ll set the following flags to auto-generate the credentials instead of setting them manually:
--serial-public-id
--generate-private-id
--generate-key
$ ykman otp yubiotp 2 --serial-public-id --generate-private-id --generate-key
Using YubiKey serial as public ID: vvcccckubcbu
Using a randomly generated private ID: 43c91024f0d1
Using a randomly generated secret key: a9ac6ee995ac33deb61166cf13c34648
Upload credential to YubiCloud? [y/N]: y
Upload to YubiCloud initiated successfully.
Program an OTP credential in slot 2? [y/N]: y
Opening upload form in browser: https://upload.yubico.com/proceed/019b6220-18e9-48b8-b7e7-5e8a1672c848
Setting the
--upload
flag wouldn’t have prompted us to upload in the example above.
Verify that the second slot has indeed been programmed:
$ ykman otp info
Slot 1: programmed
Slot 2: programmed
How can I delete and upload new credentials from a slot?
Deleting is easy. Here we’re deleting the contents we just added in the previous question:
$ ykman otp delete 2
Do you really want to delete the configuration of slot 2? [y/N]: y
Deleting the configuration of slot 2...
However, if intending to upload it to YubiCloud, we’ll have to create another public id. Unfortunately, you can’t delete a public id from Yubicloud. No big deal, we’ll just create the new credentials without the --serial-public-id
flag which will force us to enter a new one:
$ ykman otp yubiotp 2 --generate-private-id --generate-key
Enter public ID: vvcccckubcbb
Using a randomly generated private ID: 1aa8db0e8853
Using a randomly generated secret key: 99a35e0fb81a1ded6e44112517ff2257
Upload credential to YubiCloud? [y/N]: y
Upload to YubiCloud initiated successfully.
Program an OTP credential in slot 2? [y/N]: y
Parsing the OTP for Fun
The following idea and C code is influenced and inspired by What does this button do? Demystifying the Yubikey!.
yubikey.c
#include <stdio.h> #define LEN_YK 45 #define LEN_ID 13 #define LEN_OTP 33 struct YK { char key[LEN_YK]; char id[LEN_ID]; char otp[LEN_OTP]; }; void get_yk(struct YK *yk) { printf("Touch YubiKey: "); scanf("%s", yk->key); int i; for (i = 0; i < LEN_ID - 1; i++) { // Subtract 1 to not include null char. yk->id[i] = yk->key[i]; } yk->id[i] = '\0'; int j; for (j = 0; yk->key[i] != '\0';) { yk->otp[j++] = yk->key[i++]; } yk->otp[j] = '\0'; } int main() { struct YK yk; get_yk(&yk); printf("\nYubiKey encoded string %s\n", yk.key); printf("YubiKey id %s\n", yk.id); printf("YubiKey OTP private id %s\n", yk.otp); return 0; }
$ gcc -o yubikey yubikey.c
$ ./yubikey
Touch YubiKey: vvbbbbbbbbbbkjdkhrjvfefkccceghkdbevnherdkcnr
YubiKey encoded string vvbbbbbbbbbbkjdkhrjvfefkccceghkdbevnherdkcnr
YubiKey id vvbbbbbbbbbb
YubiKey OTP kjdkhrjvfefkccceghkdbevnherdkcnr
~/yubikey:$ python yubikey.py
A logical next step would be to parse the OTP string into its respective parts (see the OTP generation algorithm above). This could be gathered using another struct.
Here’s the same program written in Python:
yubikey.py
def yubikey(): id_length = 12 yubikey = input("Touch YubiKey: ") print("YubiKey encoded string", yubikey) print("YubiKey ID", yubikey[:id_length]) print("YubiKey OTP", yubikey[id_length:]) if __name__ == "__main__": yubikey()
$ python yubikey.py
Touch YubiKey: vvbbbbbbbbbbnbffgebhcgkrbdjblvgvicvkjrbigvit
YubiKey encoded string vvbbbbbbbbbbnbffgebhcgkrbdjblvgvicvkjrbigvit
YubiKey ID vvbbbbbbbbbb
YubiKey OTP nbffgebhcgkrbdjblvgvicvkjrbigvit
Clearly, the Python program is a lot easier to implement. However, you don’t learn anything about memory management by using these high-level interpreted languages.