Playing around with a?Yubikey
This post is produced in collaboration with Resonance Security?—?cybersecurity for?all.
About a year ago I purchased a Yubikey, and then a second one a couple of months ago, as a backup in case the first one breaks.
In this article, I am going to be playing around with the Yubikey Security Key C NFC that I obtained from Amazon and documenting what I found. That’s another way of saying: warning, lots of technical stuff ahead.
The key supports the FIDO2 authentication standard, which enables passwordless and phishing-resistant authentication. A very simplistic description of how the key is used to perform authentication is as follows:
- The Yubikey generates a private/public key pair for a specific website or app
- On creating an account with a website, the Yubikey provides the public key to the website or app wishing to authenticate in the future
- Every time you want to log in, the website or app submits a challenge string to the key, and the key signs the challenge with the private key and returns the signature
- The website can then verify that the signature contains the expected public key and is valid, and log you in.
There are quite a few Linux command line tools out there that allow you to communicate with the Yubikey. I am using Ubuntu 24.04.2 LTS in the examples below. Although the tools are also available for Windows and MacOS, there are some minor differences you will have to look up yourself if you are using a different operating system from me.
Installing the?tools
The tools we will be using are fido2-token, fido2-cred, and fido2-assert, which are part of the fido2-tools package, and ykman, which is part of the yubikey-manager package:
sudo apt-get install yubikey-manager
sudo apt-get install fido2-tools
Extracting information
Finding where the device is?mounted
The first thing to do is find where the device is mounted. This will be a file location (because in Linux everything is technically a file), in the form /dev/hidrawX, where X is a number. We can find this out with:
fido2-token -L
As you can see, I have two Yubikeys. To make things simpler, I will unplug the 5.4.3 key, and keep the 5.7.1 USB C key for the rest of the experiments.
The device I want is at /dev/hidraw6 and this is the device identifier I will be using from now on. Your device may have a different number at the end.
Querying the device for system information
I can now list all sorts of information about the device with the following “information†(-I) command:
fido2-token -I /dev/hidraw6
Here you can see that the device supports several different protocols: U2F, various versions of FIDO2, that it is accessible through USB and NFC, and that it supports the digital signing algorithms es256, eddsa, and es384.
Every time you go to the security options for a website and register your Yubikey, it creates credentials for that site. I have registered my Yubikey at two Google sites, for two different Google accounts that I have.
Listing stored credentials
To list those credentials, we use the ykman command:
ykman fido credentials list
Each credential has an identifier (the credential ID), which is a 384 bit number. In this case it is shown in the first column as a truncated hexadecimal number. The credential has an RP ID, which stands for “relying party identifierâ€, that indicates which website domain the credential is to be used for, and then a username and a display name, which should be self-explanatory.
Not shown is the fact that the first credential is credential 00, and the second is 01, the third is 02, and so on. The Yubikey can thus support 100 credentials.
Now that I have the RP ID, I can look up more details concerning all the credentials, for example, all the chainfrog credentials:
fido2-token -L -k chainfrog /dev/hidraw6
This lists each credential for the relying party “chainfrogâ€, the credential ID encoded in base64, the display name (which I didn’t specify when generating this credential, so it’s null), the digital signing algorithm for the key, and some other stuff that is undocumented, so I have no idea what it means.
We can check that the credential ID in base64 reported by fido2-token matches that provided by ykman by decoding it:
echo "5+4yCrpSHhtIAAlpSF/yvecroRyyh4FpjWyGaZH0Y3Ni9GOVjOyPYSyPI5NdNrPa" | base64 --decode | xxd -p
You can see that the decoded first credential has a hex string starting with e7ee320a…?, as does the ykman listing we got earlier. They are the same credential.
Viewing the public?key
Now we have enough information to extract the es256 public key for the credential by providing the relying party (chainfrog) and the credential ID:
fido2-token -I -k chainfrog -i 5+4yCrpSHhtIAAlpSF/yvecroRyyh4FpjWyGaZH0Y3Ni9GOVjOyPYSyPI5NdNrPa /dev/hidraw6
We get a PEM encoded public key back:
This is a PEM encoded public key, which we can convert to a hexadecimal string (which makes it DER encoded) with the following command:
echo "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5+4yCrpSHhtIAAlpSOYVEM5UIinMtwTLuM5kAUFolfOtxTR2VvWdRlx0pxMKax1GGiXUd3HKYxqq5H3VEnzdbQ==" | base64 --decode | xxd -p
The DER encoding has a precursor, namely:
3059301306072a8648ce3d020106082a8648ce3d0301070342
which indicates that the public key is from the es256 digital signing algorithm, which is ECDSA using the NIST P-256 curve, also known as the secp256r1 curve, or the prime256v1. You may wonder why the cryptography community decided to use three different names for the same curve; I can only assume that it’s an obfuscation attempt at keeping cryptography out of the hands of the average person.
Unfortunately, this choice of elliptic curve means a Yubikey like this one can’t be used for Bitcoin or Ethereum signing, because that requires ECDSA with the secp256k1 curve?—?note the k rather than r at the end of the curve name, indicating it’s a different elliptic curve.
The next part, 00, indicates that the public key in uncompressed form follows, and the actual public key is:
04e7ee320aba521e1b4800096948e61510ce542229ccb704cbb8ce6401416895f3adc5347656f59d465c74a7130a6b1d461a25d47771ca631aaae47dd5127cdd6d
You can find out more about ECDSA signing algorithms and elliptic curves at Professor Bill Buchanan’s site, here and here, which includes forms for pasting in PEM or DER certificates and extracting the key.
Creating your own credential
You don’t have to register your key with a website to add credentials to it.
There is a command called fido2-cred that allows you to create and verify credentials, and in fact, I used it to create the chainfrog credentials for the purposes of this article.
First we need to construct a file that contains the credentials parameters. Unfortunately, I haven’t been able to find out how to add a display name for credentials generated with fido2-cred, but we can create the minimum required credentials generation file from the command line by piping the required parameters to a file, which I have called cred_param.
echo "this is a credential challenge" | openssl sha256 -binary | base64 > cred_param
echo chainfrog >> cred_param
echo kf106 >> cred_param
dd if=/dev/urandom bs=1 count=32 | base64 >> cred_param
Each credential has to be generated with an initial challenge, which is supposed to be a hash of a stringified JSON object containing a random string, a string comprising the protocol, domain, and port of the web application requesting the credential be created, and string specifying what type of operation is being performed, but in practice any old hashed string will do, because the key is handed a hash, i.e. a 256 bit value represented as a base64 string. Therefore, any old base64 encoded 256 bit number will do.
Then we feed the parameter file to the fido2-cred command to register the new credentials:
fido2-cred -M -r -i cred_param /dev/hidraw6
The -M means “create credentialsâ€, the -r means “store them on the Yubikey for later use, and the -i specifies the input file that contains the parameters.
Note that you have to touch the Yubikey to allow the command to generate the credentials. This is to stop people from generating credentials on your Yubikey remotely. These days, some of the more expensive keys contain a biometric reader to ensure that the person touching the key is the right person, for extra security.
Signing a challenge
The primary aim of the Yubikey is to digitally sign challenges. This is called an “assertion�—?you are asserting that you are who you say you are, and are providing proof of the assertion with the digital signature.
And so we can use the fido2-assert command to pass a challenge to the key for signing. We will put the assert parameters in a file calledassert_param, for which we will need the id of the credentials to use.
First to start making assert_param:
echo "random thing to be signed" | openssl sha256 -binary | base64 > assert_param
echo chainfrog >> assert_param
Then to get the credential id:
fido2-token -L -k chainfrog /dev/hidraw6
Finally, add the credential id we get from the previous command to the end of the assert parameters:
echo "5+4yCrpSHhtIAAlpSF/yvecroRyyh4FpjWyGaZH0Y3Ni9GOVjOyPYSyPI5NdNrPa" >> assert_param
Now we can feed the assert_param file to fido2-assert and using the -G flag we get back a signature:
fido2-assert -G -i assert_param /dev/hidraw6
Okay, so what has been returned there?
Line 1: the challenge data hash, which is the same as the first line of our assert_param file.
Line 2: the relying party name, which is the same as the second line of our assert_param file.
Line 3: is called “authenticator data†which contains a hash of the relying party identifier (in my case, chainfrog), some flags indicating details about the hardware used, and a counter that is increased every time the device performs a signing act. This is to ensure that the Yubikey never signs the same message twice, because there is a vulnerability in ECDSA signing that allows private keys to be extracted if you can get two different signatures of the same data.
Line 4: this is the bit we are really interested in. It is a signature, generated using the private key of the credential, and from signing the client data hash.
Verifying the signature
The fido2-assert can be used with the -V flag to verify an assertion.
First let’s generate the public key file by getting the PEM public key data from the Yubikey and writing it to pub.pem. Here we are retrieving the credential details from the Yubikey, and writing all but the first line to a file using the tail command:
fido2-token -I -k chainfrog -i 5+4yCrpSHhtIAAlpSF/yvecroRyyh4FpjWyGaZH0Y3Ni9GOVjOyPYSyPI5NdNrPa /dev/hidraw6 | tail -n +2 > pub.pem
Next, as before, we generate an assertion from our assert_paramsfile and write the output to assert_output:
fido2-assert -G -i assert_param > assert_output
Finally, we verify that the assertion output is valid against the public key:
cat assert_output | fido2-assert -V pub.pem es256
echo $?
Annoyingly, fido2-assert -V doesn’t give any output if the assertion is valid, so the echo command shows the exit status of the previous command, which will be 0 if the assertion was validated.
The challenge
I have tried to verify the assertion signature with openssl instead of fido2-assert, but haven’t managed to get that to work. The problem I am facing is that I cannot find any documentation that explains what I am supposed to be verifying the signature against. What exactly did I sign?
To perform our verification with openssl you use something like:
openssl pkeyutl -verify -in hash.bin -sigfile sig.bin -inkey pub.pem -pubin
In this, hash.bin is a binary file of a hash of the data that was signed, the sig.bin file is a binary file of the digital signature, and the pub.pem file contains the public key in PEM format.
# This gets the public key into pub.pem
fido2-token -I -k chainfrog -i 5+4yCrpSHhtIAAlpSF/yvecroRyyh4FpjWyGaZH0Y3Ni9GOVjOyPYSyPI5NdNrPa /dev/hidraw6 | tail -n +2 > pub.pem
# This takes the signature from assert_output and decodes it to binary
tail -n 1 assert_output | base64 -d > sig.bin
Getting the signature and the public key sorted is easy, but although I have tried all sorts of combinations of the first three lines ofassert_output data to find the right hash.bin, then converting it to binary, concatenating it, and SHA256ing it in various ways, I have been unable to find the right combination to get a positive verification of the signature. Documentation hasn’t helped, and the fido2-assert function code hasn’t helped me either.
If you can work it out, I would be very interested, so please leave a comment if you succeed!
Event & Sales Manager @Holvi | Co-Founder @Jobited
6 天å‰Mario Bodemann
I help tech companies hire tech talent
1 周Keir, insightful post indeed. What inspired this topic?
SVP, Strategic Partnerships I Former CFO & COO I Ranked #1 for LinkedIn Community Growth (USA) I Author, The Future of Community I Speaker and LinkedIn Community Building Strategist
1 周Awesome post per usual Keir Finlow-Bates!
?? Founder, Tokenomics.net. Free Tokenomics Template & Course in "Featured". Co-Founder of CONV3RT, A Creative Agency for Web3 Brands.
1 周Interesting and haven't seen this before! Will check it out and appreciate the content my friend.
Account Management & Customer Success - Banking | Fintech | Crypto | SaaS
1 周?? Technical, but not so much that a non-technical person can't follow along and get a general concept of the process. Appreciate you sprinkling kernels of dry humor throughout ?? keeps me reading!