// Tutorial //

How To Set Up Multi-Factor Authentication for SSH on Ubuntu 14.04

Published on September 29, 2015
Default avatar
By Michael Holley
Developer and author at DigitalOcean.
How To Set Up Multi-Factor Authentication for SSH on Ubuntu 14.04
Not using Ubuntu 14.04?Choose a different version or distribution.

This article has been updated for Ubuntu 16.04. The updated version also includes additional information on how to go beyond basic setup.

Introduction

An authentication factor is a single piece of information used to to prove you have the rights to perform an action, like logging into a system. An authentication channel is the way an authentication system delivers a factor to the user or requires the user to reply. Passwords and security tokens are examples of authentication factors; computers and phones are examples of channels.

SSH uses passwords for authentication by default, and most SSH hardening instructions recommend using an SSH key instead. However, this is still only a single factor. If a bad actor has compromised your computer, then they can use your key to compromise your servers as well.

To combat that, in this tutorial, we’ll set up multi-factor authentication. Multi-factor authentication (MFA) requires more than one factor in order to authenticate, or log in. This means a bad actor would have to compromise multiple things, like both your computer and your phone, to get in. The different type of factors are often summarized as:

  1. Something you know, like a password or security question
  2. Something you have, like an authenticator app or security token
  3. Something you are, like your fingerprint or voice

One common factor is an OATH-TOTP app, like Google Authenticator. OATH-TOTP (Open Authentication Time-Based One-Time Password) is an open protocol that generates a one-time use password, commonly a 6 digit number that is recycled every 30 seconds.

This article will go over how to enable SSH authentication using an OATH-TOTP app in addition to an SSH key. Logging into your server via SSH will then require two factors across two channels, thereby making it more secure than a password or SSH key alone.

Prerequisites

To follow this tutorial, you will need:

  • One Ubuntu 14.04 Droplet.

  • A sudo non-root user with an SSH key added, which you can set up by following this Initial Server Setup tutorial.

  • A smartphone or tablet with an OATH-TOTP app installed, like Google Authenticator (iOS, Android).

Step 1 — Installing libpam-google-authenticator

In this step, we’ll install and configure Google’s PAM.

PAM, which stands for Pluggable Authentication Module, is an authentication infrastructure used on Linux systems to authenticate a user. Because Google made an OATH-TOTP app, they also made a PAM that generates TOTPs and is fully compatible with any OATH-TOTP app.

First, update Ubuntu’s repository cache.

  1. sudo apt-get update

Next, install the PAM.

  1. sudo apt-get install libpam-google-authenticator

With the PAM installed, we’ll use a helper app that got installed with the PAM to generate a TOTP key for the user you want to add a second factor to. This key is generated on a user by user basis, not system wide. This means every user that wants to use a TOTP auth app will need to log in and run the helper app to get their own key.

  1. google-authenticator

After you run the command, you’ll be asked a few questions. The first one asks if authentication tokens should be time-based.

This PAM allows for time-based or sequential-based tokens. Using sequential-based tokens mean the code starts at a certain point and then increments the code after every use. Using time-based tokens mean the code changes randomly after a certain time elapses. We’ll stick with time-based because that is what apps like Google Authenticator anticipate, so answer yes.

Do you want authentication tokens to be time-based (y/n) y

After answering this question, a lot of output will scroll past, including a large QR code. Make sure you record the secret key, verification code, the emergency scratch codes in a safe place, like a password manager.

At this point, use your authenticator app on your phone to scan the QR code or manually type in the secret key. If the QR code is too big to scan, you can use the URL above the QR code to get a smaller version. Once it’s added, you’ll see a six digit code that changes every 30 seconds in your app.

The remaining questions inform the PAM how to function. We’ll go through them one by one.

Do you want me to update your "~/.google_authenticator" file (y/n) y

This basically writes the key and options to the .google_authenticator file. If you say no, the program quits and nothing is written, which means the authenticator won’t work.

Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n) y

By answering yes here, you are preventing a replay attack by making each code expire immediately after use. This prevents an attacker from capturing a code you just used and logging in with it.

By default, tokens are good for 30 seconds and in order to compensate for
possible time-skew between the client and the server, we allow an extra
token before and after the current time. If you experience problems with poor
time synchronization, you can increase the window from its default
size of 1:30min to about 4min. Do you want to do so (y/n) n

Answering yes here allows up to 8 valid codes in a moving four minute window. By answering no, we limit it to 3 valid codes in a 1:30 minute rolling window. Unless you find issues with the 1:30 minute window, no is the more secure choice.

If the computer that you are logging into isn't hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting (y/n) y

Rate limiting means a remote attacker can only attempt a certain number of guesses before being blocked. If you haven’t previously configured rate limiting directly into SSH, doing so now is a great hardening technique.

Step 2 — Configuring OpenSSH

The next step now is to configure SSH to use your TOTP key. We’ll need to tell SSH about the PAM and then configure SSH to use it.

First, open up the sshd configuration file for editing using nano or your favorite text editor.

  1. sudo nano /etc/pam.d/sshd

Add the following line to the bottom of the file.

. . .
# Standard Un*x password updating.
@include common-password
auth required pam_google_authenticator.so nullok

The “nullok” word on the end tells PAM that this authentication method is optional. This allows users without a OATH-TOTP key to still log in using their SSH key. Once all users have an OATH-TOTP key, you can delete “nullok” on this line to make it MFA mandatory.

Save and close the file.

Next, we’ll configure SSH to support this kind of authentication. Open the SSH configuration file for editing.

  1. sudo nano /etc/ssh/sshd_config

Look for ChallengeResponseAuthentication and set its value to yes.

. . .
# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
ChallengeResponseAuthentication yes
. . .

Save and close the file, then restart SSH to reload the configuration files.

  1. sudo service ssh restart

Step 3 — Making SSH Aware of MFA

In this step, we’ll test if the SSH key works.

First, open another terminal and try SSHing into the server now. You’ll notice that you logged into this second session using your SSH key, without entering your verification code or password. This is because an SSH key overrides all other authentication options by default. We need to tell SSH to use the TOTP code and to use your SSH key in place of your password.

Now, open the sshd configuration file again.

  1. sudo nano /etc/ssh/sshd_config

Locate the PasswordAuthentication line, uncomment it by deleting the # character the head of the line, and update its value to no. This tells SSH not to prompt for a password.

. . .
# Change to no to disable tunnelled clear text passwords
PasswordAuthentication no
. . .

Next, add the following line at the bottom of the file. This tells SSH which authentication methods are required.

. . .
UsePAM yes
AuthenticationMethods publickey,keyboard-interactive

Save and close the file.

Next, open the PAM sshd configuration file.

  1. sudo nano /etc/pam.d/sshd

Find the line @include common-auth and comment it out by adding a # character as the first character on the line. This tells PAM not to prompt for a password; we previously told SSH not to in sshd_config.

. . .
# Standard Un*x authentication.
#@include common-auth
. . .

Save and close the file, then restart SSH.

  1. sudo service ssh restart

Now try logging into the server again. You should see that you authenticated partially with your SSH key and then got prompted for your verification code. It will look like this:

Example login output
ssh sammy@your_server_ip

Authenticated with partial success.
Verification code:

Enter your verification code from your OAUTH-TOTP ap, and you’ll log into the server. You now have MFA enabled for SSH!

Conclusion

As with any system that you harden and secure, you become responsible for managing that security. In this case, that means not losing your SSH key or your TOTP secret key. However, sometimes things happen, and you can lose control of the keys to get you in.

Here are a few suggestions to regain access to your server:

  • If you lose, or don’t have access to, your TOTP app, use your recovery codes as a verification code. This happens if you get a new phone and forgot to export your keys out of the old one, or if your phone runs out of power.

  • If you lose your secret key and the backup, use the console via the DigitalOcean control panel to log in. Then either rename or delete the file ~/.google_authenticator. This will make sure PAM is unaware of your configuration, and won’t prompt you for a code. Make sure that /etc/pam.d/sshd still has “nullok” added, like in step 2; if you change this, make sure to restart SSH.

  • If you lose your SSH key, use the console again to log in and remove your old public from ~/.ssh/authorized_hosts. Then, you can either replace it with a new key.

By having two factors (an SSH key + MFA token) across two channels (your computer + your phone), you’ve made it nearly impossible for an outside agent to brute force their way into your machine via SSH and greatly increased the security of your machine.


Want to learn more? Join the DigitalOcean Community!

Join our DigitalOcean community of over a million developers for free! Get help and share knowledge in our Questions & Answers section, find tutorials and tools that will help you grow as a developer and scale your project or business, and subscribe to topics of interest.

Sign up
About the authors
Default avatar
Developer and author at DigitalOcean.

Default avatar
senior technical writer

hi! i write do.co/docs now, but i used to be the senior tech editor publishing tutorials here in the community.


Still looking for an answer?

Was this helpful?
10 Comments

I was thinking about doing the same thing and moving the google_authenticator file to another host. How secure is this approach? If a hacker steals the contents of google_authenticator file is he going to be able to map it on his phone and generate tokens?

This comment has been deleted

I’m getting Permission denied (keyboard-interactive) after Verification code entered

I had ssh key set, after google auth do I need another one?

Now I can’t enter into my server and that’s pretty huge problem.

How do I fix this?

This comment has been deleted

thanks for the helpful tutorial. is there a good practice to automate this for each user using cloud-config ?

One thing that might present a problem is when using a CI/CD environment.

Local Environment/Build → Push to Master Branch on Github → CI/CD Notices Change, Runs Build → Success will SSH into server using key and update web content

I’m curious to see if anyone has any thoughts?

This is really cool! Is there any way to allow login with either a password and libpam-google-authenticator or SSH key?

Is it possible to apply 2FA to only selected users? I have a service I’m trying to use that can’t support the 2FA so I need to disable it for a specific user. Thanks,

Thank you …I made a mistake not having a second terminal logged in. Lesson learned I was able to get in using the console and reverted the changed back to original state.

Hi I followed the steps above and now I am not able to log into my VM. I am getting Permission denied (publickey), I am using Secure Shell to log in. I am not even getting prompted to enter google authentication code. Please help!

Below is the message I am getting.

There is no right to privacy on this device. Permission denied (publickey). NaCl plugin exited with status code 255. ®econnect, ©hoose another connection, or E(x)it?

Below is the full output with ssh -v

OpenSSH_6.4, OpenSSL 1.0.1e-fips 11 Feb 2013 debug1: Reading configuration data /etc/ssh/ssh_config debug1: /etc/ssh/ssh_config line 51: Applying options for * debug1: Connecting to MYIP [MYIP] port 22. debug1: Connection established. debug1: identity file /home/device_admin/.ssh/id_rsa type -1 debug1: identity file /home/device_admin/.ssh/id_rsa-cert type -1 debug1: identity file /home/device_admin/.ssh/id_dsa type -1 debug1: identity file /home/device_admin/.ssh/id_dsa-cert type -1 debug1: identity file /home/device_admin/.ssh/id_ecdsa type -1 debug1: identity file /home/device_admin/.ssh/id_ecdsa-cert type -1 debug1: Enabling compatibility mode for protocol 2.0 debug1: Local version string SSH-2.0-OpenSSH_6.4 debug1: Remote protocol version 2.0, remote software version OpenSSH_6.7p1 Debian-5 debug1: match: OpenSSH_6.7p1 Debian-5 pat OpenSSH* debug1: SSH2_MSG_KEXINIT sent debug1: SSH2_MSG_KEXINIT received debug1: kex: server->client aes128-ctr hmac-sha1-etm@openssh.com none debug1: kex: client->server aes128-ctr hmac-sha1-etm@openssh.com none debug1: sending SSH2_MSG_KEX_ECDH_INIT debug1: expecting SSH2_MSG_KEX_ECDH_REPLY debug1: Server host key: ECDSA debug1: Host ‘MYIP’ is known and matches the ECDSA host key. debug1: Found key in /home/device_admin/.ssh/known_hosts:28 debug1: ssh_ecdsa_verify: signature correct debug1: SSH2_MSG_NEWKEYS sent debug1: expecting SSH2_MSG_NEWKEYS debug1: SSH2_MSG_NEWKEYS received debug1: Roaming not allowed by server debug1: SSH2_MSG_SERVICE_REQUEST sent debug1: SSH2_MSG_SERVICE_ACCEPT received Unauthorized access to this machine is prohibited. Disconnect IMMEDIATELY if you aren’t an authorized user with a valid account. All activities performed on this device may be logged, and violations of this policy may result in disciplinary action, and may be reported to law enforcement authorities.

There is no right to privacy on this device. debug1: Authentications that can continue: publickey debug1: Next authentication method: publickey debug1: Trying private key: /home/device_admin/.ssh/id_rsa debug1: Trying private key: /home/device_admin/.ssh/id_dsa debug1: Trying private key: /home/device_admin/.ssh/id_ecdsa debug1: No more authentication methods to try. Permission denied (publickey).