Skip to content

Commit

Permalink
Use variable for computing buffer length. (#60)
Browse files Browse the repository at this point in the history
* Use variable for computing buffer length.

Base offset of truncate method on buffer size, which is directly related to
the hash algorithm output size. Add test coverage using test vectors from
RFC 6238, which covers SHA1, SHA256, and SHA512.

See #59

* Update src/test/java/org/cryptacular/generator/TOTPGeneratorTest.java

Co-authored-by: Daniel Fisher <dfisher@vt.edu>
  • Loading branch information
serac and dfish3r authored Jan 25, 2022
1 parent bef8a9f commit 3419f66
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,17 @@ protected int generateInternal(final byte[] key, final long count)


/**
* Truncates HMAC output onto an unsigned (i.e. 31-bit) integer.
* Truncates HMAC output onto an unsigned (i.e. 31-bit) integer using the strategy discussed in RFC 4226,
* section 5.3.
*
* @param hmac HMAC output.
*
* @return Truncated output.
*/
private int truncate(final byte[] hmac)
{
final int offset = hmac[19] & 0xf;
// Offset is the lowest 4 bits of the computed hash
final int offset = hmac[hmac.length - 1] & 0xf;
return
(hmac[offset] & 0x7f) << 24 |
(hmac[offset + 1] & 0xff) << 16 |
Expand Down
33 changes: 31 additions & 2 deletions src/main/java/org/cryptacular/generator/TOTPGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ public class TOTPGenerator extends AbstractOTPGenerator
/** Digest algorithm specification. */
private Spec<Digest> digestSpecification = new DigestSpec("SHA1");

/**
* Current system time in seconds since the start of the epoch, 1970-01-01T00:00:00.
* This value is used if and only if it is a non-negative value; otherwise the current system time is used.
*/
private long currentTime = -1;

/** Reference start time, T0. Default 0, i.e. 1970-01-01T00:00:00. */
private int startTime;

Expand Down Expand Up @@ -97,8 +103,7 @@ public void setTimeStep(final int seconds)
*/
public int generate(final byte[] key)
{
final int unixTime = (int) (System.currentTimeMillis() / 1000);
final int t = (unixTime - startTime) / timeStep;
final long t = (currentTime() - startTime) / timeStep;
return generateInternal(key, t);
}

Expand All @@ -108,4 +113,28 @@ protected Digest getDigest()
{
return digestSpecification.newInstance();
}


/**
* Sets the current time (supports testing). This value is used if and only if it is a non-negative value; otherwise
* the current system time is used.
*
* @param epochSeconds Seconds since the start of the epoch, 1970-01-01T00:00:00.
*/
protected void setCurrentTime(final long epochSeconds)
{
currentTime = epochSeconds;
}


/**
* @return Current system time in seconds since the start of epoch, 1970-01-01T00:00:00.
*/
protected long currentTime()
{
if (currentTime >= 0) {
return currentTime;
}
return System.currentTimeMillis() / 1000;
}
}
77 changes: 77 additions & 0 deletions src/test/java/org/cryptacular/generator/TOTPGeneratorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* See LICENSE for licensing and NOTICE for copyright. */
package org.cryptacular.generator;

import java.nio.charset.StandardCharsets;
import org.cryptacular.FailListener;
import org.cryptacular.spec.DigestSpec;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

/**
* Unit test for {@link HOTPGenerator}.
*
* @author Middleware Services
*/
@Listeners(FailListener.class)
public class TOTPGeneratorTest
{
/** Test vectors from RFC 6238, appendix B. */
@DataProvider(name = "test-data-rfc6238")
public Object[][] getTestDataRfc6238()
{
// Key size is equal to hash length for test vectors in RFC-6238
// (via careful review of the main method in the reference implementation under Appendix A)
final String sha1Key = "12345678901234567890";
final String sha256Key = "12345678901234567890123456789012";
final String sha512Key = "1234567890123456789012345678901234567890123456789012345678901234";
return
new Object[][] {
{new DigestSpec("SHA1"), sha1Key, 59, 8, 94287082},
{new DigestSpec("SHA256"), sha256Key, 59, 8, 46119246},
{new DigestSpec("SHA512"), sha512Key, 59, 8, 90693936},
{new DigestSpec("SHA1"), sha1Key, 1111111109, 8, 7081804},
{new DigestSpec("SHA256"), sha256Key, 1111111109, 8, 68084774},
{new DigestSpec("SHA512"), sha512Key, 1111111109, 8, 25091201},
{new DigestSpec("SHA1"), sha1Key, 1111111111, 8, 14050471},
{new DigestSpec("SHA256"), sha256Key, 1111111111, 8, 67062674},
{new DigestSpec("SHA512"), sha512Key, 1111111111, 8, 99943326},
{new DigestSpec("SHA1"), sha1Key, 1234567890, 8, 89005924},
{new DigestSpec("SHA256"), sha256Key, 1234567890, 8, 91819424},
{new DigestSpec("SHA512"), sha512Key, 1234567890, 8, 93441116},
{new DigestSpec("SHA1"), sha1Key, 2000000000, 8, 69279037},
{new DigestSpec("SHA256"), sha256Key, 2000000000, 8, 90698825},
{new DigestSpec("SHA512"), sha512Key, 2000000000, 8, 38618901},
{new DigestSpec("SHA1"), sha1Key, 20000000000L, 8, 65353130},
{new DigestSpec("SHA256"), sha256Key, 20000000000L, 8, 77737706},
{new DigestSpec("SHA512"), sha512Key, 20000000000L, 8, 47863826},
};
}


@Test(dataProvider = "test-data-rfc6238")
public void testGenerate(
final DigestSpec digestSpec, final String asciiKey, final long currentTime, final int otpSize, final int expected)
{
final TOTPGenerator generator = new TOTPGenerator();
generator.setDigestSpecification(digestSpec);
generator.setStartTime(0);
generator.setTimeStep(30);
generator.setCurrentTime(currentTime);
generator.setNumberOfDigits(otpSize);
assertEquals(generator.generate(asciiKey.getBytes(StandardCharsets.US_ASCII)), expected);
}

/** Ensure the system time is used by default. */
@Test
public void testTimeBehavior() throws Exception
{
final TOTPGenerator generator = new TOTPGenerator();
final long t1 = generator.currentTime();
Thread.sleep(1001);
final long t2 = generator.currentTime();
assertTrue(t2 > t1);
}
}

0 comments on commit 3419f66

Please sign in to comment.