diff --git a/README.md b/README.md index 79e36e9..9b402a3 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ final TimeBasedOneTimePasswordGenerator totp = new TimeBasedOneTimePasswordGener To actually generate time-based one-time passwords, you'll need a key and a timestamp. Secure key management is beyond the scope of this document; for the purposes of an example, though, we'll generate a random key: ```java -final Key key; +final SecretKey key; { final KeyGenerator keyGenerator = KeyGenerator.getInstance(totp.getAlgorithm()); diff --git a/src/benchmark/java/com/eatthepath/otp/HmacOneTimePasswordGeneratorBenchmark.java b/src/benchmark/java/com/eatthepath/otp/HmacOneTimePasswordGeneratorBenchmark.java index 2f579ba..d3ed8eb 100644 --- a/src/benchmark/java/com/eatthepath/otp/HmacOneTimePasswordGeneratorBenchmark.java +++ b/src/benchmark/java/com/eatthepath/otp/HmacOneTimePasswordGeneratorBenchmark.java @@ -6,15 +6,15 @@ import org.openjdk.jmh.annotations.State; import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; import java.security.InvalidKeyException; -import java.security.Key; import java.security.NoSuchAlgorithmException; @State(Scope.Benchmark) public class HmacOneTimePasswordGeneratorBenchmark { private HmacOneTimePasswordGenerator hotp; - private Key key; + private SecretKey key; private int counter = 0; diff --git a/src/main/java/com/eatthepath/otp/HmacOneTimePasswordGenerator.java b/src/main/java/com/eatthepath/otp/HmacOneTimePasswordGenerator.java index 1918e36..94d2635 100644 --- a/src/main/java/com/eatthepath/otp/HmacOneTimePasswordGenerator.java +++ b/src/main/java/com/eatthepath/otp/HmacOneTimePasswordGenerator.java @@ -21,10 +21,10 @@ package com.eatthepath.otp; import javax.crypto.Mac; +import javax.crypto.SecretKey; import javax.crypto.ShortBufferException; import java.nio.ByteBuffer; import java.security.InvalidKeyException; -import java.security.Key; import java.security.NoSuchAlgorithmException; import java.util.Locale; @@ -153,7 +153,7 @@ private static String getFormatString(final int passwordLength) { * * @throws InvalidKeyException if the given key is inappropriate for initializing the {@link Mac} for this generator */ - public int generateOneTimePassword(final Key key, final long counter) throws InvalidKeyException { + public int generateOneTimePassword(final SecretKey key, final long counter) throws InvalidKeyException { final Mac mac = getMac(); final ByteBuffer buffer = ByteBuffer.allocate(mac.getMacLength()); @@ -196,7 +196,7 @@ private Mac getMac() { * * @see Locale#getDefault() */ - public String generateOneTimePasswordString(final Key key, final long counter) throws InvalidKeyException { + public String generateOneTimePasswordString(final SecretKey key, final long counter) throws InvalidKeyException { return this.generateOneTimePasswordString(key, counter, Locale.getDefault()); } @@ -212,7 +212,7 @@ public String generateOneTimePasswordString(final Key key, final long counter) t * * @throws InvalidKeyException if the given key is inappropriate for initializing the {@link Mac} for this generator */ - public String generateOneTimePasswordString(final Key key, final long counter, final Locale locale) throws InvalidKeyException { + public String generateOneTimePasswordString(final SecretKey key, final long counter, final Locale locale) throws InvalidKeyException { return this.formatOneTimePassword(generateOneTimePassword(key, counter), locale); } @@ -235,7 +235,7 @@ public String generateOneTimePasswordString(final Key key, final long counter, f * * @see HOTP: An HMAC-Based One-Time Password Algorithm (RFC 4226) - Security Requirements */ - public boolean validateOneTimePassword(final Key key, final long counter, final String oneTimePassword) throws InvalidKeyException { + public boolean validateOneTimePassword(final SecretKey key, final long counter, final String oneTimePassword) throws InvalidKeyException { if (oneTimePassword == null) { throw new NullPointerException("One-time password must not be null"); } @@ -274,7 +274,7 @@ public boolean validateOneTimePassword(final Key key, final long counter, final * * @see HOTP: An HMAC-Based One-Time Password Algorithm (RFC 4226) - Security Requirements */ - public boolean validateOneTimePassword(final Key key, final long counter, final int oneTimePassword) throws InvalidKeyException { + public boolean validateOneTimePassword(final SecretKey key, final long counter, final int oneTimePassword) throws InvalidKeyException { return generateOneTimePassword(key, counter) == oneTimePassword; } diff --git a/src/main/java/com/eatthepath/otp/TimeBasedOneTimePasswordGenerator.java b/src/main/java/com/eatthepath/otp/TimeBasedOneTimePasswordGenerator.java index 40dd5ed..e3c3bd9 100644 --- a/src/main/java/com/eatthepath/otp/TimeBasedOneTimePasswordGenerator.java +++ b/src/main/java/com/eatthepath/otp/TimeBasedOneTimePasswordGenerator.java @@ -21,8 +21,8 @@ package com.eatthepath.otp; import javax.crypto.Mac; +import javax.crypto.SecretKey; import java.security.InvalidKeyException; -import java.security.Key; import java.security.NoSuchAlgorithmException; import java.time.Duration; import java.time.Instant; @@ -34,7 +34,7 @@ * RFC 6238.

* *

{@code TimeBasedOneTimePasswordGenerator} instances are thread-safe and may be shared between threads. Note that - * the {@link #generateOneTimePassword(Key, Instant)} method (and its relatives) are {@code synchronized}; in + * the {@link #generateOneTimePassword(SecretKey, Instant)} method (and its relatives) are {@code synchronized}; in * multi-threaded applications that make heavy use of a shared {@code TimeBasedOneTimePasswordGenerator} instance, * synchronization may become a performance bottleneck. In that case, callers may benefit from using one * {@code TimeBasedOneTimePasswordGenerator} instance per thread (for example, with a {@link ThreadLocal}).

@@ -158,7 +158,7 @@ private static Duration validateTimeStep(final Duration timeStep) { * * @throws InvalidKeyException if the given key is inappropriate for initializing the {@link Mac} for this generator */ - public int generateOneTimePassword(final Key key, final Instant timestamp) throws InvalidKeyException { + public int generateOneTimePassword(final SecretKey key, final Instant timestamp) throws InvalidKeyException { return this.hotp.generateOneTimePassword(key, getCounterValue(timestamp)); } @@ -175,7 +175,7 @@ public int generateOneTimePassword(final Key key, final Instant timestamp) throw * * @see Locale#getDefault() */ - public String generateOneTimePasswordString(final Key key, final Instant timestamp) throws InvalidKeyException { + public String generateOneTimePasswordString(final SecretKey key, final Instant timestamp) throws InvalidKeyException { return this.generateOneTimePasswordString(key, timestamp, Locale.getDefault()); } @@ -190,7 +190,7 @@ public String generateOneTimePasswordString(final Key key, final Instant timesta * * @throws InvalidKeyException if the given key is inappropriate for initializing the {@link Mac} for this generator */ - public String generateOneTimePasswordString(final Key key, final Instant timestamp, final Locale locale) throws InvalidKeyException { + public String generateOneTimePasswordString(final SecretKey key, final Instant timestamp, final Locale locale) throws InvalidKeyException { return this.hotp.formatOneTimePassword(this.generateOneTimePassword(key, timestamp), locale); } @@ -213,7 +213,7 @@ public String generateOneTimePasswordString(final Key key, final Instant timesta * * @see TOTP: Time-Based One-Time Password Algorithm (RFC 6238) - Security Considerations */ - public boolean validateOneTimePassword(final Key key, final Instant timestamp, final String oneTimePassword) throws InvalidKeyException { + public boolean validateOneTimePassword(final SecretKey key, final Instant timestamp, final String oneTimePassword) throws InvalidKeyException { return hotp.validateOneTimePassword(key, getCounterValue(timestamp), oneTimePassword); } @@ -234,7 +234,7 @@ public boolean validateOneTimePassword(final Key key, final Instant timestamp, f * * @see TOTP: Time-Based One-Time Password Algorithm (RFC 6238) - Security Considerations */ - public boolean validateOneTimePassword(final Key key, final Instant timestamp, final int oneTimePassword) throws InvalidKeyException { + public boolean validateOneTimePassword(final SecretKey key, final Instant timestamp, final int oneTimePassword) throws InvalidKeyException { return hotp.validateOneTimePassword(key, getCounterValue(timestamp), oneTimePassword); } diff --git a/src/main/java/overview.html b/src/main/java/overview.html index c1ecef3..bb4a2bd 100644 --- a/src/main/java/overview.html +++ b/src/main/java/overview.html @@ -36,12 +36,12 @@

Usage

To actually generate time-based one-time passwords, you'll need a secret key and a timestamp. Secure key management is beyond the scope of this document; for the purposes of an example, though, we'll generate a random key:

-
final Key secretKey;
+        
final SecretKey key;
 {
     final KeyGenerator keyGenerator = KeyGenerator.getInstance(totp.getAlgorithm());
     keyGenerator.init(160);
 
-    secretKey = keyGenerator.generateKey();
+    key = keyGenerator.generateKey();
 }

Armed with a secret key, we can deterministically generate one-time passwords for any timestamp:

diff --git a/src/test/java/com/eatthepath/otp/ExampleApp.java b/src/test/java/com/eatthepath/otp/ExampleApp.java index 1c9b8e5..7a9512e 100644 --- a/src/test/java/com/eatthepath/otp/ExampleApp.java +++ b/src/test/java/com/eatthepath/otp/ExampleApp.java @@ -22,8 +22,8 @@ import javax.crypto.KeyGenerator; import javax.crypto.Mac; +import javax.crypto.SecretKey; import java.security.InvalidKeyException; -import java.security.Key; import java.security.NoSuchAlgorithmException; import java.time.Instant; @@ -31,7 +31,7 @@ public class ExampleApp { public static void main(final String[] args) throws NoSuchAlgorithmException, InvalidKeyException { final TimeBasedOneTimePasswordGenerator totp = new TimeBasedOneTimePasswordGenerator(); - final Key key; + final SecretKey key; { final KeyGenerator keyGenerator = KeyGenerator.getInstance(totp.getAlgorithm()); diff --git a/src/test/java/com/eatthepath/otp/HmacOneTimePasswordGeneratorTest.java b/src/test/java/com/eatthepath/otp/HmacOneTimePasswordGeneratorTest.java index 0c95b72..4a19bb3 100644 --- a/src/test/java/com/eatthepath/otp/HmacOneTimePasswordGeneratorTest.java +++ b/src/test/java/com/eatthepath/otp/HmacOneTimePasswordGeneratorTest.java @@ -25,10 +25,10 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; -import java.security.Key; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Locale; @@ -41,7 +41,7 @@ class HmacOneTimePasswordGeneratorTest { - private static final Key HOTP_KEY = + private static final SecretKey HOTP_KEY = new SecretKeySpec("12345678901234567890".getBytes(StandardCharsets.US_ASCII), HmacOneTimePasswordGenerator.HOTP_HMAC_ALGORITHM); diff --git a/src/test/java/com/eatthepath/otp/TimeBasedOneTimePasswordGeneratorTest.java b/src/test/java/com/eatthepath/otp/TimeBasedOneTimePasswordGeneratorTest.java index f47887e..e89496a 100644 --- a/src/test/java/com/eatthepath/otp/TimeBasedOneTimePasswordGeneratorTest.java +++ b/src/test/java/com/eatthepath/otp/TimeBasedOneTimePasswordGeneratorTest.java @@ -26,10 +26,10 @@ import org.junit.jupiter.params.provider.MethodSource; import javax.crypto.Mac; +import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; -import java.security.Key; import java.security.NoSuchAlgorithmException; import java.time.Duration; import java.time.Instant; @@ -102,7 +102,7 @@ void generateOneTimePassword(final String algorithm, final byte[] keyBytes, fina new TimeBasedOneTimePasswordGenerator(Duration.ofSeconds(30), 8, algorithm); final Instant timestamp = Instant.ofEpochSecond(epochSeconds); - final Key key = new SecretKeySpec(keyBytes, algorithm); + final SecretKey key = new SecretKeySpec(keyBytes, algorithm); assertEquals(expectedOneTimePassword, totp.generateOneTimePassword(key, timestamp)); } @@ -139,7 +139,7 @@ void generateOneTimePasswordString(final String algorithm, final byte[] keyBytes new TimeBasedOneTimePasswordGenerator(Duration.ofSeconds(30), 8, algorithm); final Instant timestamp = Instant.ofEpochSecond(epochSeconds); - final Key key = new SecretKeySpec(keyBytes, algorithm); + final SecretKey key = new SecretKeySpec(keyBytes, algorithm); assertEquals(expectedOneTimePassword, totp.generateOneTimePasswordString(key, timestamp)); } @@ -176,7 +176,7 @@ void generateOneTimePasswordStringLocale(final String algorithm, final byte[] ke new TimeBasedOneTimePasswordGenerator(Duration.ofSeconds(30), 8, algorithm); final Instant timestamp = Instant.ofEpochSecond(epochSeconds); - final Key key = new SecretKeySpec(keyBytes, algorithm); + final SecretKey key = new SecretKeySpec(keyBytes, algorithm); assertEquals(expectedOneTimePassword, totp.generateOneTimePasswordString(key, timestamp, locale)); } @@ -210,7 +210,7 @@ private static Stream generateOneTimePasswordStringLocale() { void validateOneTimePasswordInt() throws InvalidKeyException { final TimeBasedOneTimePasswordGenerator totp = new TimeBasedOneTimePasswordGenerator(); final Instant timestamp = Instant.now(); - final Key key = + final SecretKey key = new SecretKeySpec(HMAC_SHA1_KEY_BYTES, TimeBasedOneTimePasswordGenerator.TOTP_ALGORITHM_HMAC_SHA1); assertTrue(totp.validateOneTimePassword(key, timestamp, totp.generateOneTimePassword(key, timestamp))); @@ -223,7 +223,7 @@ void validateOneTimePasswordInt() throws InvalidKeyException { @Test void validateOneTimePasswordString() throws InvalidKeyException { final TimeBasedOneTimePasswordGenerator totp = new TimeBasedOneTimePasswordGenerator(); - final Key key = + final SecretKey key = new SecretKeySpec(HMAC_SHA1_KEY_BYTES, TimeBasedOneTimePasswordGenerator.TOTP_ALGORITHM_HMAC_SHA1); // A timestamp of 1970-01-01T00:18:00Z with a default TOTP generator produces a one-time password of "003784"