How can I enable (and require) n-factor authentication for all SSH connections to a CentOS 7 server?

I have read about Google Authenticator, which provides two factor authentication. But that is limited to a pin provided by an app that you have to install in each user's mobile device.

How can I add variable numbers of authentication factors? Some examples of additional factors might include:

1.) a pin code emailed to the administrator's work email address
2.) a USB key
3.) a pin texted to the administrator using a tool like twilio
etc.) ...

A Java program to send the pin emails and texts would be easy enough to build, and could be called by a shell script that replaces the default ssh authentication script, but I have never written a shell script before.

What would the shell code look like to accomplish this, assuming the Java is already written? And where would I place the shell script? The pseudo-code for the shell script might include the following steps:

1.) Trigger the Java program to send email, text, etc. with custom pins 
        when administrator types ssh [email protected]
2.) Replace the login password prompt with a series of prompts to  
        get the pins/credentials from
2.) Possibly interact with a client program to get the usb signature
3.) Send the credentials to the java program, with time stamp to ensure  
        login was done within a minute after java sent the pins
4.) Compare the Boolean returned by the java program with the CentOS password check
5.) Either authenticate or reject the user

My guess as to a first draft of a shell script might be:

USER_NAME=#How do I populate this?
TEXT_PIN=shuf -i 1-10000 -n 1
EMAIL_PIN=shuf -i 1-10000 -n 1
java -cp $CLASSPATH my.package.SendPinsClass TEXT_PIN EMAIL_PIN
if [ $? -eq 0 ]
    echo -n "We were not able to send authentication credentials.  Please log the current time and report this to the chief administrator. "
    echo -n "Pin numbers have been sent to your email and phone number.  Please check your email and text messages now before continuing the authentication process. "

echo -n "Enter Pin number from cell phone: "
echo -n "Enter Pin number from email: "
java -cp $CLASSPATH my.package.AuthenticationClass USER_NAME TEXT_PIN EMAIL_PIN
if [ $? -eq 0 ]

What else goes in the shell script? And how to I put it in place?

The specifications for an answer are given above. However, comments suggesting easier tools instead of rolling one's own are also welcome as comments.


Based on my ongoing research, I just added more explicit detail to the rough draft of the shell script above to document the steps that the script must take in order to authenticate the user. Key steps can be assumed to successfully be completed by two "black box" java programs that are called by the shell script. The major remaining questions that must be answered before this can be completed include:

1.) How does the script receive and populate the value for the USER_NAME variable?
2.) How does the script check the user's CentOS 7 OS password, and 
        how should the password be integrated into the script's authentication 
        process?  Note the security of the password needs to be protected.
3.) How does the script process the approval or rejection of the  
        authentication request with the CentOS 7 operating system?
4.) How is the working shell script placed in the authentication  
        process to replace the default authentication script?  

Note: If implementation of the script in CentOS 7 involves bundling the script as a PAM module as @steve suggests in his comment, then the accepted answer needs to give explicit instructions not only for altering the shell script, but also for integrating the shell script into the PAM module and any other aspects of the authentication process. The answer needs to work on CentOS 7.


Per this link which was posted in a comment by @steve below, the contents of /etc/pam.d/sshd are by default as follows:

auth       required     pam_sepermit.so
auth       substack     password-auth
auth       include  postlogin
account    required     pam_nologin.so
account    include  password-auth
password   include  password-auth
# pam_selinux.so close should be the first session rule
session    required     pam_selinux.so close
session    required     pam_loginuid.so
# pam_selinux.so open should only be followed by sessions to be executed in the user context
session    required     pam_selinux.so open env_params
session    optional     pam_keyinit.so force revoke
session    include  password-auth
session    include  postlogin

Note that, in CentOS 7, /lib/security/ does not exist, and is thus not the location of PAM modules, as in @steve's link, which is for Debian. Thus, without the PAM modules, we cannot store the shell script in my OP as /lib/security/2ndfactor.so. The tutorial in @steve's link suggests the following code to assign the script to /lib/security/2ndfactor.so. How do we change the following code?

apt-get update
apt-get install build-essential libpam0g-dev libcurl4-openssl-dev
gcc -fPIC -lcurl -c 2ndfactor.c
ld -lcurl -x --shared -o /lib/security/2ndfactor.so 2ndfactor.o

@steve's link also suggests that we edit /etc/pam.d/sshd to add a reference to the 2ndfactor.o after @common-auth as follows:

# PAM configuration for the Secure Shell service
# Read environment variables from /etc/environment and
# /etc/security/pam_env.conf.
auth       required     pam_env.so # [1]
# In Debian 4.0 (etch), locale-related environment variables were moved to
# /etc/default/locale, so read that as well.
auth       required     pam_env.so envfile=/etc/default/locale

# Standard Un*x authentication.
@include common-auth
auth       required     2ndfactor.so base_url=http://my.server.com/send_code.php code_size=5

But, as you can see above, the /etc/pam.d/sshd that comes default in CentOS 7 does not contain @include common-auth. Also, @steve's link uses a web url for a php script, but I am trying to call a java program from a shell script. There must be a way to do this without exposing a web url.

Lastly, @steve's link says to set ChallengeResponseAuthentication = yes in /etc/ssd/sshd_config, but in CentOS 7 there is no /etc/ssd/ directory.

Can someone please show how to adapt all this to work with CentOS 7 and Java?

  • This can be basically achieved using PAM modules
    – Jakuje
    Aug 18, 2015 at 20:47
  • Good example of writing a PAM module for extra authentication at ben.akrin.com/?p=1068
    – steve
    Aug 18, 2015 at 21:48

1 Answer 1


The standard *nix authentication stack uses PAM. You'll want to look into building a module for PAM: http://www.linux-pam.org/Linux-PAM-html/

Typically multi-factor auth vendors will provide one. For example, here's a how-to guide for implementing google authenticator as a second factor for SSH login: http://spod.cx/blog/two-factor-ssh-auth-with-pam_oath-google-authenticator.shtml

You can configure PAM to support a variety of different factors as sufficient or mandatory.

EDIT: I'm not trying to be difficult - I'm trying to redirect your research. From your second edit, you ask for help adapting a set of lines which includes: gcc -fPIC -lcurl -c 2ndfactor.c This is the giveaway. You cannot compile a shell script. PAM modules are written in C and compiled (in this example) with gcc. You might be able to call a java application from inside a PAM module, but that's outside the scope of this site and outside the scope of my knowledge.

Other notes:

  • The PAM modules for a modern CentOS version will be in /lib64/security/, done in case you need 32 and 64 bit libraries
  • Lastly, @steve's link says to set ChallengeResponseAuthentication = yes in /etc/ssd/sshd_config, but in CentOS 7 there is no /etc/ssd/ directory. This is probably just a typo and should be /etc/sshd

Alternative: As a sys admin, I discourage the use of "weird" configurations. PAM is the well-known and accepted method of authentication, and a lot of applications besides sshd can and will use it.

However, if you are only concerned about implementing 2FA for sshd in tty sessions, you could look at using the ForceCommand option in your sshd configuration: http://ben.akrin.com/?p=1290

You should note that it is relatively easy for an authenticated user to later circumvent future attempts to enforce 2FA via ForceCommand, so this is not really a good solution: https://serverfault.com/questions/639771/how-secure-is-ssh-forcecommand-on-a-jump-host

  • This is also no longer a Unix question, it's a programming question. I linked to the module writer's guide.
    – Jesse K
    Aug 18, 2015 at 21:01
  • 1
    @codemed Just because you don't like being told that your original question was poorly informed doesn't mean this is a bad answer. The "instructions" you ask for don't exist because the way you say you're trying to set things up won't work. You were trying to use GCC to compile a shell script, for cryin' out loud. Mar 30, 2016 at 5:37
  • 1
    @Codemed to clarify: 2FA is not a bad idea, and there is no reason it can't be done well. The way you're trying to do it isn't it, though; this answer attempts to point you in the direction of something that is likely to work. Mar 30, 2016 at 5:40

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .