1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
| package com.demo.sign.otp;
import java.nio.ByteBuffer; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.Locale; import java.util.stream.Collectors; import java.util.stream.Stream;
import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec;
public class HOTP {
private static final int DEFAULT_PASSWORD_LENGTH = 6; private static final String HOTP_HMAC_ALGORITHM = "HmacSHA1";
private final Mac prototypeMac; private final int passwordLength; private final int modDivisor; private final String formatString;
public HOTP() throws NoSuchAlgorithmException { this(DEFAULT_PASSWORD_LENGTH, HOTP_HMAC_ALGORITHM); }
public HOTP(final String algorithm) throws NoSuchAlgorithmException { this(DEFAULT_PASSWORD_LENGTH, algorithm); }
public HOTP(final int passwordLength, final String algorithm) throws NoSuchAlgorithmException { if (!PasswordLength.getValidPasswordLength().contains(passwordLength)) { throw new IllegalArgumentException("Password length must be between 6 and 8 digits."); } this.prototypeMac = Mac.getInstance(algorithm); PasswordLength pl = PasswordLength.getByPasswordLength(passwordLength); this.modDivisor = pl.getModDivisor(); this.formatString = pl.getFormatString(); this.passwordLength = passwordLength; }
private enum PasswordLength { SIX(6, 1_000_000, "%06d"), SEVEN(7, 10_000_000, "%07d"), EIGHT(8, 100_000_000, "%08d"),;
private int passwordLength; private int modDivisor; private String formatString;
private PasswordLength(int passwordLength, int modDivisor, String formatString) { this.passwordLength = passwordLength; this.modDivisor = modDivisor; this.formatString = formatString; }
public int getPasswordLength() { return passwordLength; }
public int getModDivisor() { return modDivisor; }
public String getFormatString() { return formatString; }
public static PasswordLength getByPasswordLength(int passwordLength) { for (PasswordLength pl : values()) { if (passwordLength == pl.getPasswordLength()) { return pl; } } return SIX; }
public static List<Integer> getValidPasswordLength() { return Stream.of(values()).map(PasswordLength::getPasswordLength).collect(Collectors.toList()); } }
public int genHOTP(final String data, final long counter) throws Exception { return genHOTP(new SecretKeySpec(data.getBytes(), "AES"), counter); }
public int genHOTP(final Key key, final long counter) throws Exception { final Mac mac = getMac(); final ByteBuffer buffer = ByteBuffer.allocate(mac.getMacLength()); buffer.putLong(0, counter);
final byte[] array = buffer.array(); mac.init(key); mac.update(array, 0, 8); mac.doFinal(array, 0);
final int offset = buffer.get(buffer.capacity() - 1) & 0x0f; return (buffer.getInt(offset) & 0x7fffffff) % this.modDivisor; }
private Mac getMac() { try { return (Mac)this.prototypeMac.clone(); } catch (CloneNotSupportedException e) { try { return Mac.getInstance(this.prototypeMac.getAlgorithm()); } catch (final NoSuchAlgorithmException ex) { throw new RuntimeException(ex); } } }
public String genHOTPString(final String data, final long counter) throws Exception { return this.genHOTPString(data, counter, Locale.getDefault()); }
public String genHOTPString(final String data, final long counter, final Locale locale) throws Exception { return this.formatOTP(genHOTP(data, counter), locale); }
public String genHOTPString(final Key key, final long counter) throws Exception { return this.genHOTPString(key, counter, Locale.getDefault()); }
public String genHOTPString(final Key key, final long counter, final Locale locale) throws Exception { return this.formatOTP(genHOTP(key, counter), locale); }
private String formatOTP(final int oneTimePassword, final Locale locale) { return String.format(locale, formatString, oneTimePassword); }
public int getPasswordLength() { return this.passwordLength; }
public String getAlgorithm() { return this.prototypeMac.getAlgorithm(); } }
|