Skip to content

Instantly share code, notes, and snippets.

@git-init-wesley
Last active May 2, 2021 19:22
Show Gist options
  • Save git-init-wesley/446b5be471e8863e9530046ae38facf2 to your computer and use it in GitHub Desktop.
Save git-init-wesley/446b5be471e8863e9530046ae38facf2 to your computer and use it in GitHub Desktop.
package me.levasseur;
import com.google.common.hash.Hashing;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Hex;
import java.security.SecureRandom;
import java.text.MessageFormat;
/**
* Copyright © LEVASSEUR Wesley
*
* @author LEVASSEUR Wesley
*/
public class TFA {
public TFA() {
}
public static String getOTPFromSecretAsBase32(final String secretAsBase32) throws DecoderException {
return TFA.getOTPFromSecretKeyHex(Hex.encodeHexString(new Base32().decode(secretAsBase32)));
}
public static boolean validateFromSecretAsBase32(final String secretAsBase32, final String otp) throws DecoderException {
return TFA.validateSecretKeyHex(Hex.encodeHexString(new Base32().decode(secretAsBase32)), otp);
}
public static boolean validateSecretKeyHex(final String secretKeyHex, final String otp) throws DecoderException {
return TFA.validate(TFA.getStep(), secretKeyHex, otp);
}
public static String getOTPFromSecretKeyHex(final String secretKeyHex) throws DecoderException {
return TFA.getOTP(TFA.getStep(), secretKeyHex);
}
private static boolean validate(final long step, final String secretKeyHex, final String otp) throws DecoderException {
return TFA.getOTP(step, secretKeyHex).equals(otp) || TFA.getOTP(step - 1, secretKeyHex).equals(otp);
}
private static long getStep() {
return System.currentTimeMillis() / 30000L;
}
@SuppressWarnings("UnstableApiUsage")
private static String getOTP(final long step, final String secretKeyHex) throws DecoderException {
StringBuilder steps = new StringBuilder(Long.toHexString(step).toUpperCase());
while (steps.length() < 16) {
steps.insert(0, "0");
}
final byte[] hash = Hashing.hmacSha1(Hex.decodeHex((secretKeyHex))).hashBytes(Hex.decodeHex((steps.toString()))).asBytes();
final int offset = hash[hash.length - 1] & 0xf;
final int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16) | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
final int otp = binary % 1000000;
StringBuilder result = new StringBuilder(Integer.toString(otp));
while (result.length() < 6) {
result.insert(0, "0");
}
return result.toString();
}
/**
* @author levasseur.wesley (edit)
* @author thoeger (master)
*/
public static final class TFAUserData {
private static final SecureRandom secureRandom = new SecureRandom();
private String issuer;
private String user;
private byte[] secret;
public TFAUserData() {
}
public TFAUserData(final String issuer, final String user, final byte[] secret) {
this.issuer = issuer;
this.user = user;
this.secret = secret;
}
public TFAUserData(final byte[] secret) {
this(null, null, secret);
}
public static TFAUserData create() {
return new TFAUserData(TFAUserData.createSecret());
}
public static TFAUserData create(final String issuer, final String user) {
return new TFAUserData(issuer, user, TFAUserData.createSecret());
}
private static byte[] createSecret() {
byte[] secret = new byte[20];
TFAUserData.secureRandom.nextBytes(secret);
return secret;
}
public String getIssuer() {
return this.issuer;
}
public String getUser() {
return this.user;
}
public byte[] getSecret() {
return this.secret;
}
public String getSecretAsHex() {
return Hex.encodeHexString(this.secret);
}
public String getSecretAsBase32() {
return new Base32().encodeToString(this.secret);
}
public String getUrl() {
return MessageFormat.format("otpauth://totp/{0}:{1}?secret={2}&issuer={3}", this.issuer, this.user, this.getSecretAsBase32(), this.issuer);
}
public String getSerial() {
return MessageFormat.format("otpauth://totp/{0}:{1}", this.issuer, this.user);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment