Add OTP#getNextPin(), clean up code

This commit is contained in:
MrLetsplay 2024-09-07 19:48:16 +02:00
parent 1896eef707
commit 46acdedb8b
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
7 changed files with 126 additions and 142 deletions

View File

@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.cringe_studios</groupId> <groupId>com.cringe_studios</groupId>
<artifactId>CringeAuthenticatorLibrary</artifactId> <artifactId>CringeAuthenticatorLibrary</artifactId>
<version>1.6</version> <version>1.7</version>
<name>CringeAuthenticatorLibrary</name> <name>CringeAuthenticatorLibrary</name>
<description>The Library of the Cringe Authenticator</description> <description>The Library of the Cringe Authenticator</description>
<build> <build>

View File

@ -1,13 +1,12 @@
package com.cringe_studios.cringe_authenticator_library; package com.cringe_studios.cringe_authenticator_library;
import java.security.InvalidKeyException;
import com.cringe_studios.cringe_authenticator_library.impl.Base32; import com.cringe_studios.cringe_authenticator_library.impl.Base32;
import com.cringe_studios.cringe_authenticator_library.impl.TOTP;
public abstract class OTP { public abstract class OTP {
//TOTP //TOTP
//HOTP //HOTP
protected byte[] secret; protected byte[] secret;
protected String base32Secret; protected String base32Secret;
protected OTPType type; protected OTPType type;
@ -17,7 +16,7 @@ public abstract class OTP {
protected long period; protected long period;
protected boolean checksum; protected boolean checksum;
static protected long timeOffset; static protected long timeOffset;
protected OTP(OTPType nType, String nSecret, OTPAlgorithm nAlgorithm, int nDigits, long nCounter, long nPeriodInSeconds, boolean nChecksum) { protected OTP(OTPType nType, String nSecret, OTPAlgorithm nAlgorithm, int nDigits, long nCounter, long nPeriodInSeconds, boolean nChecksum) {
this.secret = Base32.decode(nSecret); this.secret = Base32.decode(nSecret);
base32Secret = nSecret; base32Secret = nSecret;
@ -28,16 +27,16 @@ public abstract class OTP {
period = (nPeriodInSeconds <= 0) ? 30 : nPeriodInSeconds; period = (nPeriodInSeconds <= 0) ? 30 : nPeriodInSeconds;
checksum = nChecksum; checksum = nChecksum;
} }
//secret: required - base32 encoded //secret: required - base32 encoded
//algorithm: optional (default SHA1) //algorithm: optional (default SHA1)
//digits: optional (default 6) //digits: optional (default 6)
//OTPType: required //OTPType: required
//period: optional (default 30) //period: optional (default 30)
//counter: required if hotp //counter: required if hotp
/** /**
* *
* @param type REQUIRED: the type of the OTP (HTOP or TOTP) * @param type REQUIRED: the type of the OTP (HTOP or TOTP)
* @param secret REQUIRED: the secret, base32 encoded * @param secret REQUIRED: the secret, base32 encoded
* @param algorithm OPTIONAL (default SHA1) the hash algorithm to use (SHA1 SHA224 SHA256 SHA384 SHA512) * @param algorithm OPTIONAL (default SHA1) the hash algorithm to use (SHA1 SHA224 SHA256 SHA384 SHA512)
@ -46,57 +45,68 @@ public abstract class OTP {
* @param periodInSeconds OPTIONAL (default 30) for TOTP, the refresh rate of the TOTP * @param periodInSeconds OPTIONAL (default 30) for TOTP, the refresh rate of the TOTP
* @param checksum OPTIONAL: appends a checksum digit to the end of the string * @param checksum OPTIONAL: appends a checksum digit to the end of the string
* @return returns a valid OTP object, which can be used to create a new OTP pin * @return returns a valid OTP object, which can be used to create a new OTP pin
*/ */
public static OTP createNewOTP(OTPType type, String secret, OTPAlgorithm algorithm, int digits, long counter, long periodInSeconds, boolean checksum) throws OTPException{ public static OTP createNewOTP(OTPType type, String secret, OTPAlgorithm algorithm, int digits, long counter, long periodInSeconds, boolean checksum) throws OTPException{
if(type == null) throw new OTPException("No OTP Type given!"); if(type == null) throw new OTPException("No OTP Type given!");
return type.instance(secret, algorithm, digits, counter, periodInSeconds, checksum); return type.instance(secret, algorithm, digits, counter, periodInSeconds, checksum);
} }
public abstract long getNextDueTime(); public abstract long getNextDueTime();
/** /**
* *
* @return s the current pin, as calculated by the counter or the current time, unless it * @return the current pin, as calculated by the counter or the current time
* @throws an OTPException, which is either thrown, if you somehow create an OTP with an invalid key, despite the checks in the instantiate method, or if your platform does not support the hashing algorithm you selected * @throws OTPException is either thrown, if you somehow create an OTP with an invalid key, despite the checks in the instantiate method, or if your platform does not support the hashing algorithm you selected
*/ */
public abstract String getPin() throws OTPException; public abstract String getPin() throws OTPException;
/**
*
* @return the next pin, as calculated by the counter or the current time
* @throws OTPException is either thrown, if you somehow create an OTP with an invalid key, despite the checks in the instantiate method, or if your platform does not support the hashing algorithm you selected
*/
public abstract String getNextPin() throws OTPException;
public abstract void incrementCounter(); public abstract void incrementCounter();
public String getSecret() { public String getSecret() {
return this.base32Secret; return this.base32Secret;
} }
public OTPType getOTPType() { public OTPType getOTPType() {
return this.type; return this.type;
} }
public OTPAlgorithm getAlgorithm() { public OTPAlgorithm getAlgorithm() {
return algorithm; return algorithm;
} }
public int getDigits() { public int getDigits() {
return this.digits; return this.digits;
} }
public long getCounter() { public long getCounter() {
return this.counter; return this.counter;
} }
public long getPeriod() { public long getPeriod() {
return this.period; return this.period;
} }
public boolean hasChecksum() { public boolean hasChecksum() {
return checksum; return checksum;
} }
// Seconds towards positive infinity means a positive correction into the future (seconds = 2 -> 5 becomes 7) /**
// Seconds towards negative infinity means a negative correction into the past (seconds = -2 -> 5 becomes 3) * Adjusts the timestamps used for OTP calculations in {@link TOTP} codes relative to the system clock.<br>
* - A positive value means a positive correction into the future (seconds = 2 -> 5 becomes 7)<br>
* - A negative value means a negative correction into the past (seconds = -2 -> 5 becomes 3)
* @param seconds The amount to adjust by
*/
public static void setTimeCorrection(long seconds) { public static void setTimeCorrection(long seconds) {
timeOffset = seconds; timeOffset = seconds;
} }
public static long getTimeCorrection() { public static long getTimeCorrection() {
return timeOffset; return timeOffset;
} }

View File

@ -1,11 +1,9 @@
package com.cringe_studios.cringe_authenticator_library.impl; package com.cringe_studios.cringe_authenticator_library.impl;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import com.cringe_studios.cringe_authenticator_library.OTP; import com.cringe_studios.cringe_authenticator_library.OTP;
import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm; import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm;
@ -29,18 +27,19 @@ public class HOTP extends OTP {
} }
@Override @Override
public String getPin() throws OTPException{ public String getPin() throws OTPException {
return getPinAtCounter(getCounter());
}
@Override
public String getNextPin() throws OTPException {
return getPinAtCounter(getCounter() + 1);
}
public String getPinAtCounter(long counter) throws OTPException {
try { try {
long movingFactor = this.getCounter(); byte[] msg = ByteBuffer.allocate(Long.BYTES).putLong(counter).array();
return generateOTP(-1, msg);
byte[] text = new byte[Long.BYTES];
for (int i = text.length - 1; i >= 0; i--) {
text[i] = (byte) (movingFactor & 0xff);
movingFactor >>= 8;
}
return generateOTP(-1, text);
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
throw new OTPException("Your secret is invalid! Please rescan the code!", e); throw new OTPException("Your secret is invalid! Please rescan the code!", e);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
@ -54,10 +53,10 @@ public class HOTP extends OTP {
int codeDigits = this.getDigits(); int codeDigits = this.getDigits();
// put movingFactor value into text byte array // put movingFactor value into text byte array
int digits = addChecksum ? (codeDigits + 1) : codeDigits; int digits = addChecksum ? (codeDigits + 1) : codeDigits;
byte[] text = message; byte[] text = message;
// compute hmac hash // compute hmac hash
byte[] hashIncorrectLength = this.getAlgorithm().hash(secret, text); byte[] hashIncorrectLength = this.getAlgorithm().hash(secret, text);
@ -73,16 +72,16 @@ public class HOTP extends OTP {
| ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff); | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
//int otp = binary % DIGITS_POWER[codeDigits]; //int otp = binary % DIGITS_POWER[codeDigits];
//byte[] truncatedHash = Arrays.copyOfRange(hash, offset, offset + 3); //byte[] truncatedHash = Arrays.copyOfRange(hash, offset, offset + 3);
//truncatedHash[0] &= 0x7F; //truncatedHash[0] &= 0x7F;
//BigInteger bigBinary = new BigInteger(1, truncatedHash); //BigInteger bigBinary = new BigInteger(1, truncatedHash);
BigInteger bigBinary = BigInteger.valueOf(binary); BigInteger bigBinary = BigInteger.valueOf(binary);
BigInteger bigOtp = bigBinary.mod(BigInteger.TEN.pow(codeDigits)); BigInteger bigOtp = bigBinary.mod(BigInteger.TEN.pow(codeDigits));
//System.out.println(binary + " vs. " + bigBinary.toString()); //System.out.println(binary + " vs. " + bigBinary.toString());
//System.out.println(otp + " vs. " + bigOtp.toString()); //System.out.println(otp + " vs. " + bigOtp.toString());
if (addChecksum) { if (addChecksum) {
//otp = (otp * 10) + HOTPChecksumProvider.calcChecksum(otp, codeDigits); //otp = (otp * 10) + HOTPChecksumProvider.calcChecksum(otp, codeDigits);
bigOtp = bigOtp.multiply(BigInteger.TEN).add(BigInteger.valueOf(HOTPChecksumProvider.calcChecksum(bigOtp.longValue(), codeDigits))); bigOtp = bigOtp.multiply(BigInteger.TEN).add(BigInteger.valueOf(HOTPChecksumProvider.calcChecksum(bigOtp.longValue(), codeDigits)));

View File

@ -1,21 +1,17 @@
package com.cringe_studios.cringe_authenticator_library.impl; package com.cringe_studios.cringe_authenticator_library.impl;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm; import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm;
import com.cringe_studios.cringe_authenticator_library.OTPException; import com.cringe_studios.cringe_authenticator_library.OTPException;
import com.cringe_studios.cringe_authenticator_library.OTPType; import com.cringe_studios.cringe_authenticator_library.OTPType;
//https://datatracker.ietf.org/doc/html/rfc6238 //https://datatracker.ietf.org/doc/html/rfc6238
public class TOTP extends HOTP { public class TOTP extends HOTP {
public TOTP(String secret, OTPAlgorithm algorithm, int digits, long counter, long periodInSeconds, boolean checksum) { public TOTP(String secret, OTPAlgorithm algorithm, int digits, long counter, long periodInSeconds, boolean checksum) {
super(secret, algorithm, digits, counter, periodInSeconds, checksum); super(secret, algorithm, digits, counter, periodInSeconds, checksum);
this.type = OTPType.TOTP; this.type = OTPType.TOTP;
} }
@Override @Override
public long getNextDueTime() { public long getNextDueTime() {
// e.g. current time: 20 seconds & current period: 30 seconds -> next due time: // e.g. current time: 20 seconds & current period: 30 seconds -> next due time:
@ -24,50 +20,23 @@ public class TOTP extends HOTP {
} }
@Override @Override
public String getPin() throws OTPException{ public String getPin() throws OTPException {
try { return getPinAtTime(System.currentTimeMillis() / 1000);
return getPinAt(System.currentTimeMillis() / 1000);
} catch (InvalidKeyException e) {
throw new OTPException("Your secret is invalid! Please rescan the code!", e);
} catch (NoSuchAlgorithmException e) {
throw new OTPException("Your platform does not support the " + this.getAlgorithm().name() + "algorithm!", e);
}
} }
@Override @Override
public long getCounter() { public long getCounter() {
return getCounterAt(System.currentTimeMillis() / 1000 - super.getTimeCorrection()); return getCounterAt(System.currentTimeMillis() / 1000);
} }
private long getCounterAt(long unixSecond) { private long getCounterAt(long unixSecond) {
return unixSecond / this.getPeriod(); return (unixSecond - getTimeCorrection()) / this.getPeriod();
} }
public String getPinAt(long time) throws InvalidKeyException, NoSuchAlgorithmException { public String getPinAtTime(long unixSecond) throws OTPException {
//int codeDigits = this.getDigits(); return getPinAtCounter(getCounterAt(unixSecond));
// Get the HEX in a Byte[]
byte[] msg = ByteBuffer.allocate(Long.BYTES).putLong(this.getCounterAt(time)).array();
//byte[] k = this.secret;
//byte[] hash = this.getAlgorithm().hash(k, msg);
// put selected bytes into result int
//int offset = hash[hash.length - 1] & 0xf;
//int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16)
// | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
//int otp = binary % DIGITS_POWER[codeDigits];
//String result = Integer.toString(otp);
//while (result.length() < codeDigits) {
// result = "0" + result;
//}
//return result;
return this.generateOTP(-1, msg);
} }
@Override @Override
public void incrementCounter() {} public void incrementCounter() {}
} }

View File

@ -18,6 +18,7 @@ import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm;
import com.cringe_studios.cringe_authenticator_library.OTPException; import com.cringe_studios.cringe_authenticator_library.OTPException;
import com.cringe_studios.cringe_authenticator_library.OTPType; import com.cringe_studios.cringe_authenticator_library.OTPType;
import com.cringe_studios.cringe_authenticator_library.impl.Base32; import com.cringe_studios.cringe_authenticator_library.impl.Base32;
import com.cringe_studios.cringe_authenticator_library.impl.HOTP;
import com.cringe_studios.cringe_authenticator_library.impl.TOTP; import com.cringe_studios.cringe_authenticator_library.impl.TOTP;
public class OTPTest { public class OTPTest {
@ -32,34 +33,34 @@ public class OTPTest {
String[] expectedValues = { "755224", "287082", "359152", "969429", "338314", "254676", "287922", "162583", String[] expectedValues = { "755224", "287082", "359152", "969429", "338314", "254676", "287922", "162583",
"399871", "520489" }; "399871", "520489" };
if(VERBOSE) { if(VERBOSE) {
System.out.println("calcPin expected"); System.out.println("calcPin expected");
} }
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
int tempi = i; int tempi = i;
assertDoesNotThrow(() ->{ assertDoesNotThrow(() ->{
OTP testOTP1 = OTPType.HOTP.instance(base32secret, OTPAlgorithm.SHA1, 6, tempi, 30, false); OTP testOTP1 = OTPType.HOTP.instance(base32secret, OTPAlgorithm.SHA1, 6, tempi, 30, false);
String calculatedPin = testOTP1.getPin(); String calculatedPin = testOTP1.getPin();
if(VERBOSE) { if(VERBOSE) {
System.out.println(calculatedPin + " == " + expectedValues[tempi]); System.out.println(calculatedPin + " == " + expectedValues[tempi]);
} }
assertEquals(calculatedPin, expectedValues[tempi]); assertEquals(calculatedPin, expectedValues[tempi]);
}); });
} }
} }
@Test @Test
public void md5Test() { public void md5Test() {
String secret = String.format("%x", new BigInteger(1, "MD5".getBytes())); String secret = String.format("%x", new BigInteger(1, "MD5".getBytes()));
String base32secret = Base32.encode(hexStr2Bytes(secret)); String base32secret = Base32.encode(hexStr2Bytes(secret));
//System.out.println("MD5 String: " + base32secret); //System.out.println("MD5 String: " + base32secret);
assertDoesNotThrow(() -> { assertDoesNotThrow(() -> {
OTP testOTP1 = OTPType.HOTP.instance(base32secret, OTPAlgorithm.MD5, 6, 0, 30, false); OTP testOTP1 = OTPType.HOTP.instance(base32secret, OTPAlgorithm.MD5, 6, 0, 30, false);
for(int i = 0; i < 5; i++) { for(int i = 0; i < 5; i++) {
@ -69,10 +70,10 @@ public class OTPTest {
System.out.println("Calculated MD5 pin - counter: " + i + " pin: " + calculatedPin); System.out.println("Calculated MD5 pin - counter: " + i + " pin: " + calculatedPin);
} }
} }
}); });
} }
@Test @Test
public void lengthTest() { public void lengthTest() {
assertDoesNotThrow(() -> { assertDoesNotThrow(() -> {
@ -80,41 +81,41 @@ public class OTPTest {
String base32secret = Base32.encode(hexStr2Bytes(secret)); String base32secret = Base32.encode(hexStr2Bytes(secret));
OTP testOTP1 = OTP.createNewOTP(OTPType.HOTP, base32secret, OTPAlgorithm.SHA1, 1, 0, 0, false); OTP testOTP1 = OTP.createNewOTP(OTPType.HOTP, base32secret, OTPAlgorithm.SHA1, 1, 0, 0, false);
assertDoesNotThrow(() -> testOTP1.getPin()); assertDoesNotThrow(() -> testOTP1.getPin());
secret = "100"; secret = "100";
base32secret = Base32.encode(hexStr2Bytes(secret)); base32secret = Base32.encode(hexStr2Bytes(secret));
OTP testOTP2 = OTP.createNewOTP(OTPType.HOTP, base32secret, OTPAlgorithm.SHA256, 0, 0, 0, false); OTP testOTP2 = OTP.createNewOTP(OTPType.HOTP, base32secret, OTPAlgorithm.SHA256, 0, 0, 0, false);
assertDoesNotThrow(() -> testOTP2.getPin()); assertDoesNotThrow(() -> testOTP2.getPin());
OTP testOTP3 = OTP.createNewOTP(OTPType.HOTP, base32secret, OTPAlgorithm.SHA512, 50, 0, 0, false); OTP testOTP3 = OTP.createNewOTP(OTPType.HOTP, base32secret, OTPAlgorithm.SHA512, 50, 0, 0, false);
assertDoesNotThrow(() -> testOTP3.getPin()); assertDoesNotThrow(() -> testOTP3.getPin());
OTP testOTP4 = OTP.createNewOTP(OTPType.HOTP, base32secret, OTPAlgorithm.SHA512, 10, 0, 0, false); OTP testOTP4 = OTP.createNewOTP(OTPType.HOTP, base32secret, OTPAlgorithm.SHA512, 10, 0, 0, false);
assertDoesNotThrow(() -> testOTP4.getPin()); assertDoesNotThrow(() -> testOTP4.getPin());
assertThrows(OTPException.class, () -> { assertThrows(OTPException.class, () -> {
OTP testOTP5 = OTP.createNewOTP(OTPType.HOTP, "", OTPAlgorithm.SHA1, 8, 0, 0, false); OTP testOTP5 = OTP.createNewOTP(OTPType.HOTP, "", OTPAlgorithm.SHA1, 8, 0, 0, false);
assertNotNull(testOTP5); assertNotNull(testOTP5);
}); });
}); });
} }
@Test @Test
public void meeemstersCrashTest() { public void meeemstersCrashTest() {
assertDoesNotThrow(() -> { assertDoesNotThrow(() -> {
String secret = "s3ydtxo35tz3n3ob5dehlh76ciokhmsbbae5zusz4vm3k6wxmt6lsfvt"; String secret = "s3ydtxo35tz3n3ob5dehlh76ciokhmsbbae5zusz4vm3k6wxmt6lsfvt";
//String base32secret = Base32.encode(hexStr2Bytes(secret)); //String base32secret = Base32.encode(hexStr2Bytes(secret));
OTP testOTP1 = OTP.createNewOTP(OTPType.HOTP, secret, OTPAlgorithm.MD5, 12, 0, 1, true); OTP testOTP1 = OTP.createNewOTP(OTPType.HOTP, secret, OTPAlgorithm.MD5, 12, 0, 1, true);
assertDoesNotThrow(() -> testOTP1.getPin()); assertDoesNotThrow(() -> testOTP1.getPin());
OTP testOTP2 = OTP.createNewOTP(OTPType.TOTP, secret, OTPAlgorithm.MD5, 12, 0, 1, true); OTP testOTP2 = OTP.createNewOTP(OTPType.TOTP, secret, OTPAlgorithm.MD5, 12, 0, 1, true);
for(int i = 0; i < 1000; i++) { for(int i = 0; i < 1000; i++) {
assertDoesNotThrow(() -> testOTP2.getPin()); assertDoesNotThrow(() -> testOTP2.getPin());
testOTP2.incrementCounter(); testOTP2.incrementCounter();
} }
//OTP testOTP2 = OTP.createNewOTP(OTPType.HOTP, Base32.encode(hexStr2Bytes(secret)), OTPAlgorithm.MD5, 12, 0, 1, true); //OTP testOTP2 = OTP.createNewOTP(OTPType.HOTP, Base32.encode(hexStr2Bytes(secret)), OTPAlgorithm.MD5, 12, 0, 1, true);
//assertDoesNotThrow(() -> testOTP2.getPin()); //assertDoesNotThrow(() -> testOTP2.getPin());
}); });
@ -131,7 +132,7 @@ public class OTPTest {
ret[i] = bArray[i + 1]; ret[i] = bArray[i + 1];
return ret; return ret;
} }
@Test @Test
public void totpTest() { public void totpTest() {
String expectedValues[] = { String expectedValues[] = {
@ -154,7 +155,7 @@ public class OTPTest {
"77737706", "77737706",
"47863826" "47863826"
}; };
// Seed for HMAC-SHA1 - 20 bytes // Seed for HMAC-SHA1 - 20 bytes
String base16seed = "3132333435363738393031323334353637383930"; String base16seed = "3132333435363738393031323334353637383930";
// Seed for HMAC-SHA256 - 32 bytes // Seed for HMAC-SHA256 - 32 bytes
@ -167,45 +168,45 @@ public class OTPTest {
String seed64 = Base32.encode(hexStr2Bytes(base16seed64)); String seed64 = Base32.encode(hexStr2Bytes(base16seed64));
long testTime[] = { 59L, 1111111109L, 1111111111L, 1234567890L, 2000000000L, 20000000000L }; long testTime[] = { 59L, 1111111109L, 1111111111L, 1234567890L, 2000000000L, 20000000000L };
assertDoesNotThrow(() -> { assertDoesNotThrow(() -> {
TOTP testOTP1 = (TOTP) OTPType.TOTP.instance(seed, OTPAlgorithm.SHA1, 8, 0, 30, false); TOTP testOTP1 = (TOTP) OTPType.TOTP.instance(seed, OTPAlgorithm.SHA1, 8, 0, 30, false);
TOTP testOTP256 = (TOTP) OTPType.TOTP.instance(seed32, OTPAlgorithm.SHA256, 8, 0, 30, false); TOTP testOTP256 = (TOTP) OTPType.TOTP.instance(seed32, OTPAlgorithm.SHA256, 8, 0, 30, false);
TOTP testOTP512 = (TOTP) OTPType.TOTP.instance(seed64, OTPAlgorithm.SHA512, 8, 0, 30, false); TOTP testOTP512 = (TOTP) OTPType.TOTP.instance(seed64, OTPAlgorithm.SHA512, 8, 0, 30, false);
long X = 30; long X = 30;
String steps = "0"; String steps = "0";
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
df.setTimeZone(TimeZone.getTimeZone("UTC")); df.setTimeZone(TimeZone.getTimeZone("UTC"));
try { try {
if(VERBOSE) { if(VERBOSE) {
System.out.println("+---------------+-----------------------+--------+--------+"); System.out.println("+---------------+-----------------------+--------+--------+");
System.out.println("| Time(sec) | Time (UTC format) | TOTP | Mode |"); System.out.println("| Time(sec) | Time (UTC format) | TOTP | Mode |");
System.out.println("+---------------+-----------------------+--------+--------+"); System.out.println("+---------------+-----------------------+--------+--------+");
} }
for (int i = 0; i < testTime.length; i++) { for (int i = 0; i < testTime.length; i++) {
String fmtTime = String.format("%1$-11s", testTime[i]); String fmtTime = String.format("%1$-11s", testTime[i]);
String utcTime = df.format(new Date(testTime[i] * 1000)); String utcTime = df.format(new Date(testTime[i] * 1000));
String sha1Pin = testOTP1.getPinAt(testTime[i]); String sha1Pin = testOTP1.getPinAtTime(testTime[i]);
String sha256Pin = testOTP256.getPinAt(testTime[i]); String sha256Pin = testOTP256.getPinAtTime(testTime[i]);
String sha512Pin = testOTP512.getPinAt(testTime[i]); String sha512Pin = testOTP512.getPinAtTime(testTime[i]);
if(VERBOSE) { if(VERBOSE) {
System.out.println("| " + fmtTime + " | " + utcTime + " |" + sha1Pin + "| SHA1 |"); System.out.println("| " + fmtTime + " | " + utcTime + " |" + sha1Pin + "| SHA1 |");
System.out.println("| " + fmtTime + " | " + utcTime + " |" + sha256Pin + "| SHA256 |"); System.out.println("| " + fmtTime + " | " + utcTime + " |" + sha256Pin + "| SHA256 |");
System.out.println("| " + fmtTime + " | " + utcTime + " |" + sha512Pin + "| SHA512 |"); System.out.println("| " + fmtTime + " | " + utcTime + " |" + sha512Pin + "| SHA512 |");
System.out.println("+---------------+-----------------------+--------+--------+"); System.out.println("+---------------+-----------------------+--------+--------+");
} }
assertEquals(expectedValues[i * 3 + 0], sha1Pin); assertEquals(expectedValues[i * 3 + 0], sha1Pin);
assertEquals(expectedValues[i * 3 + 1], sha256Pin); assertEquals(expectedValues[i * 3 + 1], sha256Pin);
assertEquals(expectedValues[i * 3 + 2], sha512Pin); assertEquals(expectedValues[i * 3 + 2], sha512Pin);
@ -216,6 +217,18 @@ public class OTPTest {
}); });
} }
@Test
public void testGetNextPin() {
assertDoesNotThrow(() -> {
String secret = "s3ydtxo35tz3n3ob5dehlh76ciokhmsbbae5zusz4vm3k6wxmt6lsfvt";
HOTP testOTP1 = (HOTP) OTP.createNewOTP(OTPType.HOTP, secret, OTPAlgorithm.SHA1, 12, 0, 0, true);
TOTP testOTP2 = (TOTP) OTP.createNewOTP(OTPType.TOTP, secret, OTPAlgorithm.SHA1, 12, 0, 60, true);
assertEquals(testOTP1.getNextPin(), testOTP1.getPinAtCounter(testOTP1.getCounter() + 1));
assertEquals(testOTP2.getNextPin(), testOTP2.getPinAtTime(System.currentTimeMillis() / 1000 + testOTP2.getPeriod()));
});
}
// @org.junit.jupiter.api.Test // @org.junit.jupiter.api.Test
// public void test() { // public void test() {
// hotpTest(); // hotpTest();

View File

@ -31,16 +31,9 @@
package test; package test;
import java.io.IOException; import java.security.InvalidKeyException;
import java.io.File;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.lang.reflect.UndeclaredThrowableException;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Arrays; import java.util.Arrays;
import java.security.InvalidKeyException;
import javax.crypto.Mac; import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
@ -159,7 +152,7 @@ public class ogHOTP {
// compute hmac hash // compute hmac hash
byte[] hash = hmac_sha1(secret, text); byte[] hash = hmac_sha1(secret, text);
System.out.print(Arrays.toString(hash) + " "); System.out.print(Arrays.toString(hash) + " ");
// put selected bytes into result int // put selected bytes into result int
@ -180,7 +173,7 @@ public class ogHOTP {
} }
return result; return result;
} }
public static byte[] hexStringToByteArray(String s) { public static byte[] hexStringToByteArray(String s) {
int len = s.length(); int len = s.length();
byte[] data = new byte[len / 2]; byte[] data = new byte[len / 2];
@ -190,10 +183,10 @@ public class ogHOTP {
} }
return data; return data;
} }
public static void main(String[] args) { public static void main(String[] args) {
String Secret = "3132333435363738393031323334353637383930"; // Secret = 0x3132333435363738393031323334353637383930 String Secret = "3132333435363738393031323334353637383930"; // Secret = 0x3132333435363738393031323334353637383930
byte[] byteSecret = hexStringToByteArray(Secret); byte[] byteSecret = hexStringToByteArray(Secret);
for(int i = 0; i < 10; i++) { for(int i = 0; i < 10; i++) {
try { try {
@ -204,6 +197,6 @@ public class ogHOTP {
e.printStackTrace(); e.printStackTrace();
} }
} }
} }
} }

View File

@ -1,16 +1,16 @@
package test; package test;
import java.lang.reflect.UndeclaredThrowableException; import java.lang.reflect.UndeclaredThrowableException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.TimeZone;
import javax.crypto.Mac; import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.TimeZone;
public class ogTOTPmod1 { public class ogTOTPmod1 {
@ -75,7 +75,7 @@ public class ogTOTPmod1 {
public static String generateTOTP(String key, long timein, String crypto) { public static String generateTOTP(String key, long timein, String crypto) {
int codeDigits = 8; int codeDigits = 8;
long T = timein / 30; long T = timein / 30;
// Get the HEX in a Byte[] // Get the HEX in a Byte[]