Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ A small and easy-to-use one-time password generator for Java according to [RFC 4
* [Usage](#usage)
* [HOTP (Counter-based one-time passwords)](#hotp-counter-based-one-time-passwords)
* [TOTP (Time-based one-time passwords)](#totp-time-based-one-time-passwords)
* [Recovery codes](#recovery-codes)

## Features
The following features are supported:
Expand All @@ -30,23 +31,23 @@ The following features are supported:
<dependency>
<groupId>com.github.bastiaanjansen</groupId>
<artifactId>otp-java</artifactId>
<version>1.2.2</version>
<version>1.2.3</version>
</dependency>
```

### Gradle
```gradle
implementation 'com.github.bastiaanjansen:otp-java:1.2.2'
implementation 'com.github.bastiaanjansen:otp-java:1.2.3'
```

### Scala SBT
```scala
libraryDependencies += "com.github.bastiaanjansen" % "otp-java" % "1.2.2"
libraryDependencies += "com.github.bastiaanjansen" % "otp-java" % "1.2.3"
```

### Apache Ivy
```xml
<dependency org="com.github.bastiaanjansen" name="otp-java" rev="1.2.2" />
<dependency org="com.github.bastiaanjansen" name="otp-java" rev="1.2.3" />
```

Or you can download the source from the [GitHub releases page](https://github.com/BastiaanJansen/OTP-Java/releases).
Expand Down Expand Up @@ -183,6 +184,11 @@ URI uri = totp.getURI("issuer", "account"); // otpauth://totp/issuer:account?per

```

## Recovery Codes
Often, services provide "backup codes" or "recovery codes" which can be used when the user cannot access the 2FA device anymore. Often because 2FA device is a mobile phone, which can be lost or stolen.

Because recovery code generation is not part of the specifications of OTP, it is not possible to generate recovery codes with this library and should be implemented seperately.

## Licence
OTP-Java is available under the MIT licence. See the LICENCE for more info.

Expand Down
261 changes: 136 additions & 125 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,126 +1,137 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>9</version>
</parent>

<groupId>com.github.bastiaanjansen</groupId>
<artifactId>otp-java</artifactId>
<version>1.2.2-SNAPSHOT</version>

<name>OTP-Java</name>
<description>A small and easy-to-use one-time password generator for Java according to RFC 4226 (HOTP) and RFC 6238 (TOTP).</description>

<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>ossrh</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>

<licenses>
<license>
<name>MIT Licence</name>
<url>https://github.com/BastiaanJansen/OTP-Java/blob/main/LICENSE</url>
<distribution>repo</distribution>
</license>
</licenses>

<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<scm>
<connection>scm:git:https://github.com/BastiaanJansen/OTP-Java.git</connection>
<url>http://github.com/BastiaanJansen/OTP-Java</url>
<developerConnection>scm:git:https://github.com/BastiaanJansen/OTP-Java.git</developerConnection>
</scm>

<dependencies>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<configuration>
<source>8</source>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.7</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
</build>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>9</version>
</parent>

<groupId>com.github.bastiaanjansen</groupId>
<artifactId>otp-java</artifactId>
<version>1.2.3-SNAPSHOT</version>

<name>OTP-Java</name>
<description>A small and easy-to-use one-time password generator for Java according to RFC 4226 (HOTP) and RFC 6238 (TOTP).</description>

<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>ossrh</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>

<licenses>
<license>
<name>MIT Licence</name>
<url>https://github.com/BastiaanJansen/OTP-Java/blob/main/LICENSE</url>
<distribution>repo</distribution>
</license>
</licenses>

<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<scm>
<connection>scm:git:https://github.com/BastiaanJansen/OTP-Java.git</connection>
<url>http://github.com/BastiaanJansen/OTP-Java</url>
<developerConnection>scm:git:https://github.com/BastiaanJansen/OTP-Java.git</developerConnection>
</scm>

<dependencies>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>java-hamcrest</artifactId>
<version>2.0.0.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<configuration>
<source>8</source>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.7</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
</build>
</project>
9 changes: 7 additions & 2 deletions src/main/java/com/bastiaanjansen/otp/HOTPGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,13 @@ public static HOTPGenerator fromOTPAuthURI(final URI uri) throws URISyntaxExcept
HOTPGenerator.Builder builder = new HOTPGenerator.Builder(secret.getBytes());

try {
Optional.ofNullable(query.get(URIHelper.DIGITS)).map(Integer::parseInt).ifPresent(builder::withPasswordLength);
Optional.ofNullable(query.get(URIHelper.ALGORITHM)).map(HMACAlgorithm::valueOf).ifPresent(builder::withAlgorithm);
Optional.ofNullable(query.get(URIHelper.DIGITS))
.map(Integer::parseInt)
.ifPresent(builder::withPasswordLength);
Optional.ofNullable(query.get(URIHelper.ALGORITHM))
.map(String::toUpperCase)
.map(HMACAlgorithm::valueOf)
.ifPresent(builder::withAlgorithm);
} catch (Exception e) {
throw new URISyntaxException(uri.toString(), "URI could not be parsed");
}
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/com/bastiaanjansen/otp/OTPGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ public class OTPGenerator {
* @param secret used to generate hash
*/
protected OTPGenerator(final int passwordLength, final HMACAlgorithm algorithm, final byte[] secret) {
if (!validatePasswordLength(passwordLength)) {
if (!validatePasswordLength(passwordLength))
throw new IllegalArgumentException("Password length must be between 6 and 8 digits");
}

if (secret.length <= 0)
throw new IllegalArgumentException("Secret must not be empty");

this.passwordLength = passwordLength;
this.algorithm = algorithm;
Expand Down Expand Up @@ -104,6 +106,9 @@ public boolean verify(final String code, final long counter, final int delayWind
* @throws IllegalStateException when hashing algorithm throws an error
*/
protected String generateCode(final long counter) throws IllegalStateException {
if (counter < 0)
throw new IllegalArgumentException("Counter must be greater than or equal to 0");

byte[] secretBytes = decodeBase32(secret);
byte[] counterBytes = longToBytes(counter);

Expand Down Expand Up @@ -234,7 +239,7 @@ private boolean validatePasswordLength(final int passwordLength) {
* @author Bastiaan Jansen
* @param <B> concrete builder class
*/
public abstract static class Builder<B, G> {
protected abstract static class Builder<B, G> {
/**
* Number of digits for generated code in range 6...8, defaults to 6
*/
Expand Down
9 changes: 6 additions & 3 deletions src/main/java/com/bastiaanjansen/otp/TOTPGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -255,11 +255,14 @@ public static TOTPGenerator fromOTPAuthURI(final URI uri) throws URISyntaxExcept
TOTPGenerator.Builder builder = new TOTPGenerator.Builder(secret.getBytes());

try {
Optional.ofNullable(query.get(URIHelper.DIGITS)).map(Integer::valueOf)
Optional.ofNullable(query.get(URIHelper.DIGITS))
.map(Integer::valueOf)
.ifPresent(builder::withPasswordLength);
Optional.ofNullable(query.get(URIHelper.ALGORITHM)).map(HMACAlgorithm::valueOf)
Optional.ofNullable(query.get(URIHelper.ALGORITHM))
.map(String::toUpperCase).map(HMACAlgorithm::valueOf)
.ifPresent(builder::withAlgorithm);
Optional.ofNullable(query.get(URIHelper.PERIOD)).map(Long::parseLong).map(Duration::ofSeconds)
Optional.ofNullable(query.get(URIHelper.PERIOD))
.map(Long::parseLong).map(Duration::ofSeconds)
.ifPresent(builder::withPeriod);
} catch (Exception e) {
throw new URISyntaxException(uri.toString(), "URI could not be parsed");
Expand Down
Loading