package me.brandonc.security.totp.util;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Random;
import org.apache.commons.codec.binary.Base32;
public class TOTPUtils {
private static final int SECRET_SIZE = 10;
private static final int PASS_CODE_LENGTH = 6;
private static final int INTERVAL = 30;
private static final int WINDOW = 2;
private static final String CRYPTO = "HmacSHA1";
private static final Random rand = new Random();
public static String generateSecret() {
// Allocating the buffer
byte[] buffer = new byte[SECRET_SIZE];
// Filling the buffer with random numbers.
rand.nextBytes(buffer);
// Getting the key and converting it to Base32
Base32 codec = new Base32();
byte[] secretKey = Arrays.copyOf(buffer, SECRET_SIZE);
byte[] encodedKey = codec.encode(secretKey);
return new String(encodedKey);
}
public static boolean checkCode(String secret, long code) throws NoSuchAlgorithmException, InvalidKeyException {
Base32 codec = new Base32();
byte[] decodedKey = codec.decode(secret);
// Window is used to check codes generated in the near past.
// You can use this value to tune how far you're willing to go.
int window = WINDOW;
long currentInterval = getCurrentInterval();
for (int i = -window; i <= window; ++i) {
long hash = TOTP.generateTOTP(decodedKey, currentInterval + i, PASS_CODE_LENGTH, CRYPTO);
if (hash == code) {
return true;
}
}
// The validation code is invalid.
return false;
}
private static long getCurrentInterval() {
long currentTimeSeconds = System.currentTimeMillis() / 1000;
return currentTimeSeconds / INTERVAL;
}
}