YubiKey logins with SSH

By | September 3, 2013

I have just acquired a YubiKey (the standard one) and wanted to use it to provide 2-factor authentication for SSH. In case I lose the key, I also want a fallback option so I don’t lose access to the server. The documentation for this seems a bit scattered, so here’s my reference for how I made it work.

Some things to know first:

  • HOTP is a counter-based system, where each password follows a sequence based on a counter that ticks up by one each time you generate the password.
  • TOTP is a time-based system, where a new password is generated every so often (30 seconds in this case.)

The result is that when you SSH in it will ask first for your password, and then for the one-time password (OTP) provided by the YubiKey.

There are two parts to this: setting up the YubiKey to work, and setting up the fallback using the Google Authenticator Android app. This was done on a Debian Wheezy server, but should work on anything that is similar.

Packages that are required on the server: libpam-oath (>= 1.12.4-1 or you can’t have a fallback in this way)

Packages that are required on the desktop that you’re using to configure this: yubikey-personalization-gui, oathtool, and libmime-base32-perl.

When doing this, make sure that you have a root shell open on the server so that if something breaks you can undo things.

The keys here are just examples, make your own.

Setting up the YubiKey

The SSHD and PAM configuration is based on these posts: “One Time Passwords for SSH on Ubuntu and OS X” and “2 factor authenticatie met Yubikey en OATH – Installatie op Ubuntu”.

First, use yubikey-personalisation-gui to generate a key and set up the YubiKey with it. To do this, configure it in OATH-HOTP mode, turn off the token identifier, and copy the key somewhere as you’ll need it a few times. Write this to the YubiKey.

Create a file on the server, /etc/users.oath, containing:

HOTP robin - 8a54ac40689f0bb99f306fdf186b0ef6bd153429

where robin is the username that’ll be using this key, and the big string of hex digits is the key that was generated earlier with the spaces removed.

Set the permissions on this file to be unreadable by anyone except root:

# chmod 600 /etc/users.oath

Modify /etc/pam.d/sshd such that the line:

@include common-auth

is commented out, and this is added just below it:

# Normally we would `@include common-auth`, but that leaks info with OATH
auth required pam_unix.so nullok_secure

# OATH OTP
auth required pam_oath.so usersfile=/etc/users.oath window=20

window=20 says how many times the button on the YubiKey can be pressed when it’s not logging in to the server and it’ll still work; or to put it another way, how far into the OTP sequence the module will search to try to match the one it’s been given.

Modify sshd_config to allow challenge-response in SSH:

sed -i.bak -E -e 's/(ChallengeResponseAuthentication) no/\1 yes/' /etc/ssh/sshd_config

You can verify the sequence is right by running:

$ oathtool -w10 8a54ac40689f0bb99f306fdf186b0ef6bd153429
333518
886962
...

If you press the button on the YubiKey a few times, the output should match the results of this.

Restart the SSH daemon, and it should work. First you’ll have to enter your password, then you’ll have to press the YubiKey button.

Setting up Google Authenticator

This is the fallback, in case you lose the key. It will use the time-based TOTP method rather than a counter-based sequence of codes. This is based on this mailing list thread.

First generate a random key of 10 bytes (20 hex characters):

$ head -c 1024 /dev/urandom | openssl sha1 | tail -c 21 # 21 due to newline
2c2d309a7a92e117df5a

Add a line to /etc/users.oath:

HOTP/T30 robin - 2c2d309a7a92e117df5a

The T30 tells it that this is time-based with a 30 second rotation, which the authenticator app requires.

Before we can add this key into the authenticator app, we need to convert it to base32:

$ perl -e 'use MIME::Base32 qw( RFC ); print lc(MIME::Base32::encode(pack("H*","2c2d309a7a92e117df5a")))."\n";'
fqwtbgt2slqrpx22

This takes the hex key, converts it to binary, and then converts that to base32.

In the app, go to the menu -> Set up account -> Enter provided key. Give it a name, and put the base32 string you got into the key field. Leave the selector at time-based.

To check this is working, you can run:

$ oathtool --totp -w10 2c2d309a7a92e117df5a
125557
804612
...

The sequence shown should match what the app is saying. Note that this is time dependent, so everything should have clocks that are set to within a few seconds.

Now you should be able to log in to the server by providing either the time-based code in the authenticator app, or by pressing the YubiKey button.

Other notes

Whenever a login happens, the users.oath file gets modified with the new counter value (for HTOP) and the timestamp, so be aware of that if you leave it open in an editor.

If someone messes with the YubiKey and presses the button many times (more than 20 in this example) it will end up out of sync with the server, and you should probably just reset it by replacing the relevant line in users.oath with a new key and writing that new key to the YubiKey.

There is also libpam-google-authenticator which behaves similarly, but isn’t available in Debian Wheezy.

If you have multiple servers that you want to use one key with, you need to find some way of having a central server doing the authentication to prevent counters going out of sync.

SSH keys should bypass this totally, though I haven’t tested that yet.

4 thoughts on “YubiKey logins with SSH

  1. Mike

    This is a very good article on SSH login without password. Here is another one that worked for me when I first started doing this. It’s very simple, concise and easy to understand. http://tinyurl.com/m9ztegw

  2. Robin Post author

    That’s a different thing, using SSH with keys. This is about two-factor authentication with SSH.

  3. John

    Thanks very much for your walkthough. It really helped me get up to speed with my new Yubikey NEO and using it with linux logins.

    Just one comment/question (so far) – is there any reason why you generated a 10 byte (20 hex character) TOTP instead of a 20 byte (40 hex character) like you did for your HOTP secret? I tried to use a 10 byte TOTP secret but it didn’t work for me – although my application was slightly different because I have a Yubikey NEO and so I can use the ykneo-oath JavaCard applet in the NEO to store TOTP secrets and generate TOTP codes using the Yubico Authenticator Android and Desktop apps (which is a little more secure than using Google Authenticator). I tried to enter the (Base32 version of) the 10 byte secrets into the Yubico Authenticator (Desktop app) but the resulting TOTP codes were not correct. When I generated 20 byte secrets (the same size as the HOTP secrets you generated), plugging (the Base32 encoded version of) these worked like a charm. Could’ve been just a copy and paste error on my part, but in any case (even just for the case of consistency) it might be an idea to change the code in your post that generates the TOTP secret to the following in order to generate a 20 byte secret:

    head -c 1024 /dev/urandom | openssl sha1 | tail -c 21 # 21 due to newline

  4. John

    Damn it… accidental hit the Submit button before finishing my comment….!

    I *meant* to suggest the line be changed to:

    head -c 1024 /dev/urandom | openssl sha1 | tail -c 41 # 41 due to newline

    Again, thanks for taking the time to write your article.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.