Using a yubikey for more-secure remote authentication

Version $Id: yubikey.html,v 1.15 2011/12/10 22:39:42 madhatta Exp $

The yubikey is a small USB dongle from Yubico which generates one-time passwords (OTPs) and pretends to be a USB keyboard in order to enter the OTP into the keyboard datastream. I found out about them by chance - I can't remember how - and decided to buy one for experimentation. A major feature is that, having neither a real-time clock nor a display and thus needing no batteries either, they're really rather cheap. Including postage, mine cost less than £20, and you definitely won't get a SecurID dongle for that.

Also, all the yubikey back-end software is generally available under GPL or other free licences, so you generate the secrets for your tokens, you load them onto your tokens yourself, you protect them as you see fit, and you validate them on the back-end as seems best to you. In essence, the security is your responsibility, not someone else's. I regarded that as a major nice-to-have until RSA screwed the pooch by probably losing the master list of secret seeds for their SecurID tokens. Whilst RSA is still refusing to say exactly what they lost, at least one major secure customer was compromised as a result. So I reiterate: it's always better to be in control of your own secrets. If you trust someone else to look after them, this trust may be unjustified or - even worse - you may find they won't tell you whether it was unjustified or not.

I say "more-secure" not "secure" in the title because it looks as if it'll still be single-factor authentication, as right now not all methods of validating the yubikey OTP support the use of a personal PIN as well. But yubikey authentication is still much better than straight username-password as the dongle is not easily copied, and the OTP data is, well, only usable once.

Not that this is a major issue, but I must confess that another attraction of the yubikey is that, it being lightweight, thin, and waterproof, I can wear it around my neck like a sort of missile key or digital dog-tag. Geek marine! Semper pi(nguis)!

Fast links to:

The universal principles

The yubikey uses an AES key to generate one-time passwords with a unique identifying nonce, two counters and some timer-based salt. You plug it into your USB port; the hosted system thinks it's a USB keyboard. When an OTP is required you place your bare finger gently on the teeny touch pad for a second or so, and it injects the next OTP into the keyboard stream, just as if you yourself had typed it. That makes it very easy to use with any application that takes password data, whether CLI, web client, or a custom client like PuTTY or TOAD or system-config. The back-end has to do the work of authenticating the OTP; the front-end can be very general. It's clever.

An important design feature is that the yubikey is write-only as far as the AES key is concerned. The yubikey will happily sit there and give you OTPs created with the key until the counters wrap round, but it won't give the key back to anyone. Therefore, if you install a key not known to the yubikey's user, the user can't easily produce a second device that will generate a similar OTP stream.

The yubikey comes preloaded with an AES key known to yubico, and if you plan to take advantage of their internet-accessible authentication infrastructure you may wish to leave it there. It's also perfectly possible to run your own authentication infrastructure, and if you plan to do that, you should generate your own secrets and write them to the yubikey. Or you can do both:

I'll explain this in more detail later, but one nice wrinkle of the v2 yubikeys is that they support two profiles, which I shall refer to as slot-1 and slot-2. In use, these are differentiated by length of touch on the sensor; a quick press generates a token from the slot-1 data, a press of 3-4 seconds generates one from slot-2 data. Their website doesn't make clear, but their tech support confirms, that both slots can be in OTP mode. I intend to use slot-1 myself; I'll keep the AES key secret, and build my own authentication servers. But I intend to upload slot-2's AES key to yubikey, and use the infrastructure they provide when authenticating to the world.

Putting my own AES keys into the yubikey

RTFM; the manual's at http://yubico.com/files/YubiKey_manual-2.0.pdf.

The tools to install my own secrets onto the key were already in the Fedora 12 repository, which was a pleasant surprise. I did a
yum install ykpers
and they were installed. In case your repository doesn't have them, they came from http://code.google.com/p/yubikey-personalization/ and are under the BSD licence.

I want a secret key in slot 1, for my use, and a key to be uploaded to yubico for public use in slot 2. Having had a nice chat with tech support, it was clear that there's no way at this time to move the yubico-preinstalled key from slot 1 to slot 2 (but keep an eye on this forum thread). So for now, it was clear that I had to put new keys in both slots. Since I had a working key in slot 1, I decided to start by trying to program slot 2.

For reference, here's some test output from my (yubico-provided) key in slot 1:
cccccccbkuctvnckbcrfllkgerjcetnufdedutdffuvv
cccccccbkuctfhekfubctlrvbcljutlhthbirnnhjlri
cccccccbkucthuecfihvlfcuefjtrnlghutkddbendee

so I followed what I thought was the right incantation to put a key in slot 2. These have to be done as root, by the way, since arbitrary access is required to the USB device.

Specifically, I tried to put a key on-board with

[root@risby]# ykpersonalize -2 -a 9349f5c8bbb7f3b4016039e6094b3f01 -o fixed=vvnbfekh
Firmware version 2.1.1 Touch level 1793 Program sequence 1
Configuration data to be written to key configuration 2:

fixed: m:vvnbfekh
uid: h:000000000000
key: h:9349f5c8bbb7f3b4016039e6094b3f01
acc_code: h:000000000000
ticket_flags: APPEND_CR
config_flags: STATIC_TICKET|STRONG_PW1|STRONG_PW2|MAN_UPDATE|OATH_FIXED_MODHEX1|OATH_FIXED_MODHEX2|OATH_FIXED_MODHEX

Commit? (y/n) [n]: y
after which I tried pressing the button for three seconds, and nothing came out. I found quickly that you have to unplug and replug the key after a programming exercise. Then I got:
VVn25ekhgitnhdndlfujijubvighjdebtjdgicvj
VVn25ekhgitnhdndlfujijubvighjdebtjdgicvj

Ah, OK, that's what STATIC_TICKET meant. How do I turn that off?

[root@risby]# ykpersonalize -2 -a 9349f5c8bbb7f3b4016039e6094b3f01 -o fixed=vvnbfekh -o -static-ticket
Firmware version 2.1.1 Touch level 1859 Program sequence 3
Configuration data to be written to key configuration 2:

fixed: m:vvnbfekh
uid: h:000000000000
key: h:9349f5c8bbb7f3b4016039e6094b3f01
acc_code: h:000000000000
ticket_flags: APPEND_CR
config_flags: STRONG_PW1|STRONG_PW2|MAN_UPDATE|OATH_FIXED_MODHEX1|OATH_FIXED_MODHEX2|OATH_FIXED_MODHEX

Commit? (y/n) [n]: y
which gave me output like this:
VVn25ekhbnudvedjtdegideltebhfbtidbthigin
VVn25ekhnvjgrrfvhvuhnuvgitdlvcbcjblcblft
VVn25ekhlnndnbvekdjnuifurukhijbcjfjnibbb

Well, that's all very well, but it's shorter than the standard output the pre-configured slot 1 gave me, which you can see above. Since this is the slot I plan to use with the yubico servers, I'd better make it have the same format. How about

[root@risby]# ykpersonalize -2 -a 9349f5c8bbb7f3b4016039e6094b3f01 -o fixed=vvnbfekh -o -static-ticket -o send-ref
Firmware version 2.1.1 Touch level 1795 Program sequence 3
Configuration data to be written to key configuration 2:

fixed: m:vvnbfekh
uid: h:000000000000
key: h:9349f5c8bbb7f3b4016039e6094b3f01
acc_code: h:000000000000
ticket_flags: APPEND_CR
config_flags: SEND_REF|STRONG_PW1|STRONG_PW2|MAN_UPDATE|OATH_FIXED_MODHEX1|OATH_FIXED_MODHEX2|OATH_FIXED_MODHEX

Commit? (y/n) [n]: y
which gave me
!VVn25ekhlldbdiejvfduvvjgrhlgljgtcgvjgeth
!VVn25ekhudrdbffbrgtkknlgidbujurklvdltfje

No, that doesn't look right, yet. By mistake, I nearly overwrote the key in slot 1 and discovered that the default config_flags for slot 1 were empty. So I tried to turn them all off for slot 2, with

[root@risby]# ykpersonalize -2 -o fixed=vvcccccccccc -a 00000000000000000000000000000000 -o -static-ticket -o -strong-pw1 -o -strong-pw2 -o -man-update
Firmware version 2.1.1 Touch level 1795 Program sequence 3
Configuration data to be written to key configuration 2:

fixed: m:vvcccccccccc
uid: h:000000000000
key: h:00000000000000000000000000000000
acc_code: h:000000000000
ticket_flags: APPEND_CR
config_flags: 

Commit? (y/n) [n]: y
and, mirabile dictu, that seems to have done it:
cccccccbkuctnftgflceklebrflklrkdgvrehvkcjrgj from slot 1
cccccccbkuctfntgdiehdjegckvibievdrrlngbiluki from slot 1
vvccccccccccvubjibchdivdekfitklicrevrtlckrbu from slot 2
vvccccccccccuclbbhkdkrdhrllekdfujrjduttlgbkj from slot 2

I then repeated the exercise for my two slots using strong AES keys, which I generated with
dd if=/dev/random bs=1k count=1|sha1sum
and which I don't intend to post here. I did use a fixed field starting with "vv" for slot 2, the one I intend to share with yubico, which they enable you to do here. I also added the -o uid=0123456789ab field when generating my "strong" key in slot 1, just in case leaving the UID field full of zeroes weakens the crypto (but using 12 random hex digits, not the ones above).

I uploaded my yubikey at the above link, waited five minutes, and used the demo page to confirm that they'd got it just fine.

Using my slot-2 key to authenticate to public servers

I use OpenID to authenticate to livejournal. Coincidentally, I discovered that my browser had forgotten the login credentials I use to authenticate to my current OpenID provider.

At the wiki, yubico say that Clavid provide a yubikey-enabled OpenID service. I went to their signup page and signed up very quickly using my slot-2 key and the username madhatter. I then put the following code fragment at the top of my main webpage.

<link rel="openid.server"   href="http://www.clavid.com/provider/openid">
<link rel="openid.delegate" href="http://madhatter.clavid.com">
and told LJ that I wanted to authenticate as www.teaparty.net, as I usually do. I promptly got shunted off to the Clavid please authenticate using your yubikey page, supplied my slot-2 key with a long press, and hey presto! I was reading my friends' LJ postings. Tip of the hat to Clavid: thank you!

Note to self: clavid's management portal is at https://www.clavid.com/portal, to which I may (sensibly) also authenticate with my yubikey.

Using my slot-1 key to authenticate to my own servers

Using sshd

The PAM module came from http://www.securixlive.com/yubipam/download.php and doesn't appear to be under any real development at the moment. That provides the server-side bit and it's licensed under GPL.

I unpacked it, installed the pam-devel package, did a ./configure && make then a make install as root, and followed the rest of the instructions in the INSTALL file in the distro as regards adding a yubikey auth group to group-own the /etc/yubikey file, and sgid'ing the check binary.

As per the README file in the distro, I changed my /etc/pam.d/sshd from

#%PAM-1.0
auth	   required	pam_sepermit.so
auth       include      password-auth
account    required     pam_nologin.so
[...]
to
#%PAM-1.0
auth	   required	pam_sepermit.so
auth       sufficient   pam_yubikey.so    verbose_otp
auth       include      password-auth
account    required     pam_nologin.so
[...]

I added my password to the yubikey file with

[root@risby pam.d]# ykpasswd -a --user madhatta -k abcabcabcabcabcabcabcabcabcabcab -o here i supplied a slot-1 OTP
Adding Yubikey entry for madhatta
Using public UID: 00 00 00 89 87 3d 
Using private UID: 01 23 45 67 89 ab 
Completed successfully.

I'm definitely in the database, and replay attacks definitely don't work (as they most definitely should not):

[madhatta@risby madhatta]$ ykvalidate --user madhatta ccccccjkjietjvfrcvvbvrdllrggjgdkkjvtbkddbtjc
OTP is VALID.
[madhatta@risby madhatta]$ ykvalidate --user madhatta ccccccjkjietjvfrcvvbvrdllrggjgdkkjvtbkddbtjc
OTP is INVALID!

I could have used ykpasswd without the actual key to hand if I knew the public and private UID strings, in hex. From the output above, I found that the public UID (00 00 00 89 87 3d) is the same as my modhex fixed string (ccccccjkjiet) using yubico's modhex calculator. From this I presume that
ykpasswd -a --user madhatta -k abcabcabcabcabcabcabcabcabcabcab -f 00000089873d -p 0123456789ab

would have worked just as well, though I have not tried it.

I created a local guest user and became him to avoid any issues with SSH agents, identities, etc. Then I did

[guest@risby ~]$ ssh madhatta@risby
madhatta@risby's password: 
provided the OTP password, but got
Permission denied, please try again.
while /var/log/facility/authpriv * said:
Mar  8 09:24:22 risby yk_chkpwd[22644]: password check failed for user (madhatta)
Mar  8 09:24:22 risby unix_chkpwd[22645]: password check failed for user (madhatta)
Mar  8 09:24:22 risby sshd[22641]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=risby.home.teaparty.net user=madhatta
Mar  8 09:24:24 risby sshd[22641]: Failed password for madhatta from 192.168.3.11 port 59028 ssh2

I found that I also had to enable ChallengeResponseAuthentication in /etc/ssh/sshd_config with

# Change to no to disable s/key passwords
ChallengeResponseAuthentication yes
#ChallengeResponseAuthentication no

Then I got success:

[guest@risby ~]$ ssh madhatta@risby
Yubikey OTP: ccccccjkjiethrubinjedfgcbntgrfjjbfkdditlerbc
Last login: Mon Mar  8 10:28:42 2010 from risby.home.teaparty.net
[madhatta@risby ~]$ 

I tried a reply attack by pasting the OTP into a text buffer and presenting it twice, the second time I got:

[guest@risby ~]$ ssh madhatta@risby
Yubikey OTP: ccccccjkjiethrubinjedfgcbntgrfjjbfkdditlerbc
Yubikey OTP: ccccccjkjietviinkkidricrbktviivuhikutjterlub
Last login: Mon Mar  8 10:29:40 2010 from risby.home.teaparty.net
[madhatta@risby ~]$ 
Note how the first OTP, which is the one from the previous example retried, simply fails silently; only a new, valid OTP succeeds.

Yubico also provide a PAM implementation at http://code.google.com/p/yubico-pam/wiki/YubikeyAndSSHViaPAM but this authenticates off yubico's central server, which is not desirable in this application. Shame.

Using sudo

Having done the above, changing /etc/pam.d/sudo to start

#%PAM-1.0
auth       sufficient   pam_yubikey.so
auth       include      system-auth
...
makes sudo work happily with OTPs, which is extremely useful for me.

On my colocated box, I don't currently use sudo. I'd like to, but I don't want sudoers to have the option to use password authentication; the yubikey will be mandatory. In this case, I made a copy of /etc/pam.d/system-auth called /etc/pam.d/system-yubiauth, then deleted all non-auth lines and commented out the following line in /etc/pam.d/system-yubiauth:

#auth        sufficient    /lib/security/$ISA/pam_unix.so likeauth nullok
and changed the start of /etc/pam.d/sudo to read
#%PAM-1.0
auth       sufficient   pam_yubikey.so
auth       include	system-yubiauth
account    include	system-auth
...

Now, if a user prompted for a yubikey OTP just hits return, the sudo fails instead of passing him/her to password authentication.

Using squirrelmail

Squirrelmail plugin at http://code.google.com/p/yubico-squirrelmail-plugin/, also under GPL2.

MORE NEEDED.  SCRATCH PAD STARTS:

Back to Technotes index
Back to main page