
The Cat and Mouse Game
One time password as 2FA
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:
Spring Security
Spring security is a very powerful Spring module that hides all the complexity of
- Customising the ‘WebAuthenticationDetailsSource’
- Customising the ‘AuthenticationProvider’
- 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
[code language=”java”]
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;
}
}
[/code]
Then we need to extend the authentication details source to return our TOTPWebAuthenticationDetails.
[code language=”java”]
public class TOTPWebAuthenticationDetailsSource extends WebAuthenticationDetailsSource {
@Override
public TOTPWebAuthenticationDetails buildDetails(HttpServletRequest request) {
return new TOTPWebAuthenticationDetails(request);
}
}
[/code]
Next, we need to tell Spring to use our TOTPWebAuthenticationDetailsSource. So, in the configure method of the ‘WebSecurityConfig‘ we need to add the following:
[code language=”java”]
http.formLogin().authenticationDetailsSource(new TOTPWebAuthenticationDetailsSource());
[/code]
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:
[code language=”java”]
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());
}
…
}
[/code]
DBUser is a regular JPA entity, and we are using Spring Data Repositories to load it.
[code language=”java”]
@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);
}
}
[/code]
Now that we have implemented our UserDetailsService we need to implement the ‘AuthenticationProvider‘:
[code language=”java”]
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;
}
}
[/code]
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:
[code language=”java”]
@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);
}
[/code]
Implementing the TOTP algorithm
[code language=”java”]
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;
}
}
[/code]
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
