Skip to content

Commit

Permalink
Allow custom periods in seconds in timeInHex
Browse files Browse the repository at this point in the history
Instead of forcing 30s default periods, this
change allows users to specify their own
period value using timeInHex(time, period).

Period must be positive non-zero integer.

Fixes #14
  • Loading branch information
amdelamar committed Sep 20, 2020
1 parent 5a9c809 commit 3bf2e72
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 2 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

group = 'com.amdelamar'
version = '1.2.2'
version = '1.3.0'
description = 'OTP (One Time Password) utility in Java. To enable two-factor authentication (2FA) using HMAC-based) or Time-based algorithms.'
sourceCompatibility = 1.8
targetCompatibility = 1.8
Expand Down
18 changes: 17 additions & 1 deletion src/main/java/com/amdelamar/jotp/OTP.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,23 @@ public static String timeInHex() throws IOException {
* @throws IOException when generating Unix time
*/
public static String timeInHex(long timeInMillis) throws IOException {
final long time = (long) Math.floor(Math.round(((double) timeInMillis) / 1000.0) / 30d);
return timeInHex(timeInMillis, 30);
}

/**
* A method to get a Unix Time converted to Hexadecimal using a token period.
* @param timeInMillis long (like <code>System.currentTimeMillis()</code>)
* @param periodInSec int seconds period for the time to be rounded down to
* @return String Hex time
* @throws IOException
*/
public static String timeInHex(long timeInMillis, int periodInSec) throws IOException {
double period = 1d;
if (periodInSec > 1) {
// ensure period is 1 or greater value
period = periodInSec;
}
final long time = (long) Math.floor(Math.round(((double) timeInMillis) / 1000d) / period);
final byte[] longBytes = ByteBuffer.allocate(Long.SIZE / Byte.SIZE)
.putLong(time)
.array();
Expand Down
37 changes: 37 additions & 0 deletions src/test/java/com/amdelamar/jotp/type/TOTPTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,43 @@ public void totpTests() throws IllegalArgumentException, IOException, Interrupte
assertNotEquals(code1, code3);
assertTrue(OTP.verify(secret, OTP.timeInHex(thirtyOneSecondsLater), code3, 6, Type.TOTP));
}

@Test
public void customPeriodTotpTests() throws IllegalArgumentException, IOException, InterruptedException,
InvalidKeyException, NoSuchAlgorithmException {
// Normally we'd use System.currentTimeMillis(), but
// because we can't control exactly when unit tests run
// we might fail this test because the 30s window passes
// too early. So instead, we'll pick a specific point in
// time to make this test repeatable.
long time = 1600636432L;
String secret = OTP.randomBase32(OTP.BYTES);

int period = 60; // 60sec instead of 30sec

String code1 = OTP.create(secret, OTP.timeInHex(time, period), 6, Type.TOTP);

// 60 sec window, so wait just a second
long oneSecondLater = time + 1000;

String code2 = OTP.create(secret, OTP.timeInHex(oneSecondLater, period), 6, Type.TOTP);
assertEquals(code1, code2);
assertTrue(OTP.verify(secret, OTP.timeInHex(oneSecondLater, period), code2, 6, Type.TOTP));

// If its beyond 30sec since the first OTP,
// then we will get the same base value.
long thirtyOneSecondsLater = oneSecondLater + 30000;
String code3 = OTP.create(secret, OTP.timeInHex(thirtyOneSecondsLater, period), 6, Type.TOTP);
assertEquals(code1, code3);
assertTrue(OTP.verify(secret, OTP.timeInHex(thirtyOneSecondsLater, period), code3, 6, Type.TOTP));

// If its beyond 60sec since the first OTP,
// then we will get a different base value.
long sixtyOneSecondsLater = oneSecondLater + 60000;
String code4 = OTP.create(secret, OTP.timeInHex(sixtyOneSecondsLater, period), 6, Type.TOTP);
assertNotEquals(code1, code4);
assertTrue(OTP.verify(secret, OTP.timeInHex(sixtyOneSecondsLater, period), code4, 6, Type.TOTP));
}

@Test
public void padLeft() throws InvalidKeyException, IllegalArgumentException, NoSuchAlgorithmException, IOException {
Expand Down

0 comments on commit 3bf2e72

Please sign in to comment.