Last active
May 2, 2021 19:22
-
-
Save git-init-wesley/446b5be471e8863e9530046ae38facf2 to your computer and use it in GitHub Desktop.
Simple 2FA, using https://mvnrepository.com/artifact/commons-codec/commons-codec and https://mvnrepository.com/artifact/com.google.guava/guava/28.0-jre and https://mvnrepository.com/artifact/com.googlecode.perfect-hashes/perfect-hashes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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