Securing your Spring App using 2FA

Securing your Spring App using 2FA

2a_02 Not so long ago, a good old username and password were considered more than enough to secure access to our applications and favourite web sites. But back then, nobody could have imagined the countless ways in which a hacker can now get a hold of our precious login credentials. From software exploits to social engineering, security has been drawn into the spotlight like never before, and software developers must really think hard about security when building any type of software solution. In this blog post, I’ll explain how you can secure your Spring applications using 2FA (Two Factor Authentication).

The Cat and Mouse Game

Cat-and-Mouse-1   In order to tackle slack security and combat hackers, lengthly and complex requirements for passwords have now become common place. You know what I’m talking about – “your password must be at least 20 characters, have 2 uppercase, 1 lowercase, 5 symbols, blah blah blah!“. But, with these requirements that have now been imposed on us, it’s almost impossible for any mortal to remember one password. This can cause a lot of frustration because there is an endless list of complex passwords that you need to remember. Still, hackers raised their bets, and discovered different and ingenious ways of obtaining your credentials. It is for this reason, that 2FA came into the game. This form of authentications adds an extra piece of information that you need to provide in addition to username and password. On a recent customer project, we initially had to lock down access to various web apps by IP. This had some complications since users wanted to access their apps on the go. Thus, we needed a way to provide them access without breaking the strict security regulations that any big enterprise has. 2FA came up as one possible solution for us, and our client.

One time password as 2FA

kab300610_img01There are several ways of implementing 2FA. Most of the implementations provide a one time password that changes periodically. RSA tokens are commonly used. They are physical devices that generate tokens, or one time passwords (OTP). However, in some situations they can be expensive, difficult to manage and you need to have them with you all the time. And they are very easy to misplace. Another commonly used form of OTP is tokens sent through SMS messages. This also presents some issues since there is no guaranty that you will get the message in a matter of seconds. This is true for most of the messages providers unless you can afford the kind of service that huge companies like Google, or Apple use. How many times has someone sent you a SMS, and you don’t end up receiving it until 5 minutes later!? Imagine trying to enter a token that expires every 30 seconds, but takes 2 minutes to be delivered. Yeah, that’s not really going to work too well!

TOTP

An alternative to the two methods mentioned above is the time based OTP algorithm(TOTP). What a name!! TOTP generates a token based on a common secret known by both server and the client and the current timestamp. Simply put, it calculates the SHA-1 hash of the current timestamp in seconds using the secret as key for the hashing. Prior to the hashing, the time in seconds is divided by 30, which guarantees that the result will be the identical for 30 seconds. The hash then is truncated to a 6 digits number using the last byte to calculate an offset. This offset is then used to extract 4 bytes that later will be truncated to the 6 digit number. This basically ensures that the number extracted from the hash will change its position in the hash since we are basing the offset on the last byte of the generated hash. That’s a little bit to digest, so here’s what it looks like: TOTP diagram

Spring Security

Spring security is a very powerful Spring module that hides all the complexity of Spring-Security-logosecuring a web application. This is true when you know how to configure it correctly. However, when you want to fine tune the configuration it can actually get complex real quick. We are using Spring Boot which is basically a layer on top of Spring that automates the configuration based on the assumption that you want to use the standard settings. This is known as ‘convention over configuration. In order to implement TOTP as an extra authentication step within Spring security, we need to customize several steps in the authentication procedure:

  1. Customising the ‘WebAuthenticationDetailsSource’
  2. Customising the ‘AuthenticationProvider’
  3. Implementing the TOTP algorithm

Customising the ‘WebAuthenticationDetailsSource’

Assuming you have set up Spring Boot with Spring security, the first step you need to customise is the Authentication details source. This component extracts the credentials from the HTTP request. We need to customise this so that we can extract the TOTP Key


public class TOTPWebAuthenticationDetails extends WebAuthenticationDetails {
  private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
  private Integer totpKey; 

  public TOTPWebAuthenticationDetails(HttpServletRequest request) {
      super(request);
      String totpKeyString = request.getParameter("TOTPKey");
      if (StringUtils.hasText(totpKeyString)) {
        try {
          this.totpKey = Integer.valueOf(totpKeyString);
        } catch (NumberFormatException e) {
          this.totpKey = null;
        }
      }
  } 

  public Integer getTotpKey() {
    return this.totpKey;
  }
}

Then we need to extend the authentication details source to return our TOTPWebAuthenticationDetails.


public class TOTPWebAuthenticationDetailsSource extends WebAuthenticationDetailsSource {

  @Override
  public TOTPWebAuthenticationDetails buildDetails(HttpServletRequest request) {
    return new TOTPWebAuthenticationDetails(request);
  }

} 

Next, we need to tell Spring to use our TOTPWebAuthenticationDetailsSource. So, in the configure method of the ‘WebSecurityConfig‘ we need to add the following:

http.formLogin().authenticationDetailsSource(new TOTPWebAuthenticationDetailsSource());

Customising the ‘AuthenticationProvider’

The authentication provider is responsible for loading the user details, and checking the credentials. Firstly, we need to customise the ‘UserDetailsService‘ which is responsible for loading the password, and granted roles for the user from a persistent storage. In our case, we also need to load the TOTP secret in order to run the algorithm. We are using Hibernate here to manage the persistence and the details of its configuration. However, I’m not going to delve into the details of Hibernate in this post. We implement our ‘TOTPUserDetails‘ that adds the secret to the user credentials loaded from the database:

public class TOTPUserDetails implements UserDetails {
  private String username;
  private String password;
  private boolean enabled;
  private String secret;
  private Collection authorities = new HashSet<>();

  ...
  ...
  public TOTPUserDetails(DBUser user) {
    this.username = user.getUsername();
    this.password = user.getPassword();
    this.enabled = user.isEnabled();
    this.secret = user.getSecret();
    populateAuthorities(user.getRoles());
  }
  ...

}

DBUser is a regular JPA entity, and we are using Spring Data Repositories to load it.

@Component
public class DBUserDetailsService implements UserDetailsService {

  @Autowired
  private UserRepository userRepository;

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    DBUser user = userRepository.findOne(username);
    if (user == null) {
      throw new UsernameNotFoundException(username);
    }
    return new TOTPUserDetails(user);
  }
}

Now that we have implemented our UserDetailsService we need to implement the ‘AuthenticationProvider‘:

public class TOTPAuthenticationProvider extends DaoAuthenticationProvider {
  private TOTPAuthenticator totpAuthenticator;

  @Override
  protected void additionalAuthenticationChecks(UserDetails userDetails,
                                                  UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {

    super.additionalAuthenticationChecks(userDetails, authentication);

    if (authentication.getDetails() instanceof TOTPWebAuthenticationDetails) {
      String secret = ((TOTPUserDetails) userDetails).getSecret();

      if (StringUtils.hasText(secret)) {
        Integer totpKey = ((TOTPWebAuthenticationDetails) authentication
                        .getDetails()).getTotpKey();
          if (totpKey != null) {
            try {
              if (!totpAuthenticator.verifyCode(secret, totpKey, 2)) {
                throw new BadCredentialsException("Invalid TOTP code");
              }
            } catch (InvalidKeyException | NoSuchAlgorithmException e) {
                throw new InternalAuthenticationServiceException("TOTP code verification failed", e);
            }
          } else {
              throw new MissingTOTPKeyAuthenticatorException("TOTP code is mandatory");
          }
      }
    }
  }

  public TOTPAuthenticator getTotpAuthenticator() {
    return totpAuthenticator;
  }

  public void setTotpAuthenticator(TOTPAuthenticator totpAuthenticator) {
    this.totpAuthenticator = totpAuthenticator;
  }
}

The trick here is to leave the ‘DaoAuthenticationProvider‘ to do its magic and we simply override the method additionalAuthenticationChecks(). In this case, if the secret is not present we don’t check the token. You can tighten this rule up if you want to enforce the user to have a secret. In the method additionalAuthenticationChecks(), we load the secret and the TOTP Key provided by the TOTPWebAuthenticationDetails and we run the algorithm. Finally, we need to instruct Spring to use our TOTPAuthenticationProvider. We can do that in our Spring security configuration. We just need to override the following method:

@Autowired
  private DBUserDetailsService dbUserDetailsService;

@Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    PasswordEncoder encoder = new BCryptPasswordEncoder();

    TOTPAuthenticationProvider authenticationProvider = new TOTPAuthenticationProvider();
    authenticationProvider.setPasswordEncoder(encoder);
    authenticationProvider.setUserDetailsService(dbUserDetailsService);
    authenticationProvider.setTotpAuthenticator(new TOTPAuthenticator());
    auth.authenticationProvider(authenticationProvider);
  }

Implementing the TOTP algorithm

Finally, we implement the TOTP algorithm. The variance here adds some tolerance for clocks that are out of sync. As mentioned before, the secret is shared between the server and the client, and it can be generated by putting 10 random bytes in a 10 bytes array and then base 32 encoding it. So, since the secret is base 32 encoded we then need to decode.

public class TOTPAuthenticator {

  public boolean verifyCode(String secret, int code, int variance)
         throws InvalidKeyException, NoSuchAlgorithmException {
    long timeIndex = System.currentTimeMillis() / 1000 / 30;
    byte[] secretBytes = new Base32().decode(secret);
    for (int i = -variance; i <= variance; i++) {
      long calculatedCode = getCode(secretBytes, timeIndex + i);
      if (calculatedCode == code) {
        return true;
      }
    }
    return false;
  }

  public long getCode(byte[] secret, long timeIndex)
            throws NoSuchAlgorithmException, InvalidKeyException {
    SecretKeySpec signKey = new SecretKeySpec(secret, "HmacSHA1");
    //We put the timeIndex in a bytes array
    ByteBuffer buffer = ByteBuffer.allocate(8);
    buffer.putLong(timeIndex);
    byte[] timeBytes = buffer.array();

    //Calculate the SHA1
    Mac mac = Mac.getInstance("HmacSHA1");
    mac.init(signKey);
    byte[] hash = mac.doFinal(timeBytes);

    //Calculate the offset we will use to extract our pin
    int offset = hash[19] & 0xf;
    //Clear the signed bits
    long truncatedHash = hash[offset] & 0x7f;
    //Use bits shift operations to copy the remaining 3 bytes from the array
    //and construct our number
    for (int i = 1; i < 4; i++) {
      truncatedHash <<= 8;
      truncatedHash |= hash[offset + i] & 0xff;
    }
    //Truncate to 6 digits
    return truncatedHash % 1000000;
  }
}

The core of the algorithm uses the secret and the timestamp reduced to a 30 seconds interval and performs the SHA-1 hash.  We then calculate the offset that will be used to extract our 4 bytes. Last step is truncating the number to 6 digits decimal number. You might have noticed that this algorithm uses SHA-1 hash. SHA-1 has been deemed as breakable and it will be deprecated very soon. However the TOTP algorithm removes bytes from the hash and changes it’s input every 30 seconds. This makes it almost impossible to crack it.

You can find the source code here

Conclusion

TOTP Tokens Adding TOTP gives you that extra tweak to your security needs and it’s relatively easy to implement. There are several TOTP token generators mobile apps like Google Authenticator and Authy. They basically read the secret encoded in a QR code and implement the algorithm under the hood. We found implementing 2FA very effective and convenient at Shine. It was a doodle to get it up and running. We didn’t have to depend on RSA tokens or slow SMS services, and we were able to provide better value to our customer – and most importantly – piece of mind knowing their applications were safe and secure.

6 Comments
  • timi
    Posted at 00:17h, 07 October Reply

    Code source plz , nice demo ^^

    • Pablo Caif
      Posted at 12:43h, 07 October Reply

      Hi timi,

      I’ll put the code in Github and add the link to blog.

  • sam
    Posted at 19:11h, 15 October Reply

    Hello Pablo, looks interesting !
    have you got code in github

    • Pablo Caif
      Posted at 09:35h, 16 October Reply

      I added a link to the source code in the blog. Thanks for reading it

  • dhruva023
    Posted at 01:53h, 05 November Reply

    Excellent post, just want I needed!!
    Thanks

  • J K Birks
    Posted at 01:32h, 21 December Reply

    I like the self-contained nature of a hardeare token compared to how most FIDO keys need to access a USB port (which can go against company policy).

Leave a Reply

%d bloggers like this: