Skip to content

Commit dc52067

Browse files
Add an OTP Auth URI builder; Update of dependencies
1 parent a219170 commit dc52067

File tree

12 files changed

+584
-46
lines changed

12 files changed

+584
-46
lines changed

README.md

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ This library is available at [Maven Central](https://mvnrepository.com/artifact/
2525

2626
```java
2727
// Groovy
28-
implementation 'dev.turingcomplete:kotlin-onetimepassword:2.3.0'
28+
implementation 'dev.turingcomplete:kotlin-onetimepassword:2.4.0'
2929

3030
// Kotlin
31-
implementation("dev.turingcomplete:kotlin-onetimepassword:2.3.0")
31+
implementation("dev.turingcomplete:kotlin-onetimepassword:2.4.0")
3232
```
3333

3434
### Maven
@@ -37,7 +37,7 @@ implementation("dev.turingcomplete:kotlin-onetimepassword:2.3.0")
3737
<dependency>
3838
<groupId>dev.turingcomplete</groupId>
3939
<artifactId>kotlin-onetimepassword</artifactId>
40-
<version>2.2.0</version>
40+
<version>2.4.0</version>
4141
</dependency>
4242
```
4343

@@ -173,12 +173,38 @@ There is also a helper method ```GoogleAuthenticator.createRandomSecretAsByteArr
173173
Some generators limit the length of the **plain text secret** or set a fixed number of characters. So the "Google way", which has a fixed value of 10 characters. Anything outside this range will not be handled correctly by some generators.
174174

175175

176-
#### QR Code
176+
#### Key URI Format and QR Code
177177

178-
QR codes must use a URI that follows the definition in [Key Uri Format](https://github.com/google/google-authenticator/wiki/Key-Uri-Format). The secret in this URI is the Base32-encoded one.
178+
The [Key Uri Format](https://github.com/google/google-authenticator/wiki/Key-Uri-Format) specification defines a URI which can carry all generator configuration values. This URI can be embedded inside a QR code, which makes the setup of an OTP account in OTP Apps easy and error-free.
179179

180+
This library provides the `OtpAuthUriBuilder` do generate such a URI. For example:
181+
```kotlin
182+
OtpAuthUriBuilder.forTotp(Base32().encode("secret".toByteArray()))
183+
.label("John", "Company")
184+
.issuer("Company")
185+
.digits(8)
186+
.buildToString()
187+
```
188+
Would generate the URI:
189+
```text
190+
otpauth://totp/Company:John/?issuer=Company&digits=8&secret=ONSWG4TFOQ
191+
```
192+
193+
Note that according to the specification, the Base32 padding character `=` will be removed in the `secret` parameter value.
194+
195+
All three generator are providing the method `otpAuthUriBuilder()` to create an `OtpAuthUriBuilder` which already has all the configuration values set. For example:
196+
```kotlin
197+
GoogleAuthenticator(Base32().encode("secret".toByteArray()))
198+
.otpAuthUriBuilder()
199+
.issuer("Company")
200+
.buildToString()
201+
```
202+
Would generate the URI:
203+
```text
204+
otpauth://totp/?algorithm=SHA1&digits=6&period=30&issuer=Company&secret=ONSWG4TFOQ
205+
```
180206

181-
#### Simulate the Google Authenticator
207+
### Simulate the Google Authenticator
182208

183209
The directory ```example/googleauthenticator``` contains a simple JavaFX application to simulate the Google Authenticator:
184210

build.gradle.kts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ import java.net.URI
22

33
plugins {
44
`java-library`
5-
kotlin("jvm") version "1.3.41"
6-
id("org.jetbrains.dokka") version "1.4.32"
5+
kotlin("jvm") version "1.7.10"
6+
id("org.jetbrains.dokka") version "1.7.10"
77

88
signing
99
`maven-publish`
1010
}
1111

1212
allprojects {
1313
group = "dev.turingcomplete"
14-
version = "2.3.0"
14+
version = "2.4.0"
1515

1616
repositories {
1717
mavenLocal()
@@ -56,15 +56,15 @@ dependencies {
5656
implementation(kotlin("stdlib"))
5757
implementation("commons-codec:commons-codec:1.15")
5858

59-
val jUnitVersion = "5.8.2"
59+
val jUnitVersion = "5.9.0"
6060
testImplementation("org.junit.jupiter:junit-jupiter-params:$jUnitVersion")
6161
testImplementation("org.junit.jupiter:junit-jupiter-api:$jUnitVersion")
6262
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$jUnitVersion")
6363

64-
testImplementation("com.github.bastiaanjansen:otp-java:1.3.0") {
64+
testImplementation("com.github.bastiaanjansen:otp-java:1.3.2") {
6565
because("For `OtherLibrariesComparisonTest`")
6666
}
67-
testImplementation("com.eatthepath:java-otp:0.3.1") {
67+
testImplementation("com.eatthepath:java-otp:0.4.0") {
6868
because("For `OtherLibrariesComparisonTest`")
6969
}
7070
testImplementation("com.j256.two-factor-auth:two-factor-auth:1.3") {

src/main/kotlin/dev/turingcomplete/kotlinonetimepassword/GoogleAuthenticator.kt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import java.util.concurrent.TimeUnit
1313
*
1414
* @param base32secret the shared Base32-encoded-encoded secret.
1515
*/
16-
class GoogleAuthenticator(base32secret: ByteArray) {
16+
class GoogleAuthenticator(private val base32secret: ByteArray) {
1717
// -- Companion Object -------------------------------------------------------------------------------------------- //
1818

1919
companion object {
@@ -40,6 +40,8 @@ class GoogleAuthenticator(base32secret: ByteArray) {
4040
val randomSecret = RandomSecretGenerator().createRandomSecret(10)
4141
return Base32().encode(randomSecret)
4242
}
43+
44+
val CONFIG = TimeBasedOneTimePasswordConfig(30, TimeUnit.SECONDS, 6, HmacAlgorithm.SHA1)
4345
}
4446

4547
// -- Properties -------------------------------------------------------------------------------------------------- //
@@ -49,10 +51,7 @@ class GoogleAuthenticator(base32secret: ByteArray) {
4951
// -- Initialization ---------------------------------------------------------------------------------------------- //
5052

5153
init {
52-
val hmacAlgorithm = HmacAlgorithm.SHA1
53-
val config = TimeBasedOneTimePasswordConfig(30, TimeUnit.SECONDS, 6, hmacAlgorithm)
54-
55-
timeBasedOneTimePasswordGenerator = TimeBasedOneTimePasswordGenerator(Base32().decode(base32secret), config)
54+
timeBasedOneTimePasswordGenerator = TimeBasedOneTimePasswordGenerator(Base32().decode(base32secret), CONFIG)
5655
}
5756

5857
@Deprecated("Use ByteArray representation",
@@ -82,6 +81,13 @@ class GoogleAuthenticator(base32secret: ByteArray) {
8281
return code == generate(timestamp)
8382
}
8483

84+
/**
85+
* Creates an [OtpAuthUriBuilder], which pre-configured with the secret, as
86+
* well as the fixed Google authenticator configuration for the algorithm,
87+
* code digits and time step.
88+
*/
89+
fun otpAuthUriBuilder(): OtpAuthUriBuilder.Totp = timeBasedOneTimePasswordGenerator.otpAuthUriBuilder()
90+
8591
// -- Private Methods --------------------------------------------------------------------------------------------- //
8692
// -- Inner Type -------------------------------------------------------------------------------------------------- //
8793
}

src/main/kotlin/dev/turingcomplete/kotlinonetimepassword/HmacOneTimePasswordConfig.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package dev.turingcomplete.kotlinonetimepassword
22

3-
import java.lang.IllegalArgumentException
4-
53
/**
64
* The configuration for the [HmacOneTimePasswordGenerator].
75
*
@@ -20,7 +18,7 @@ import java.lang.IllegalArgumentException
2018
*
2119
* @throws IllegalArgumentException if `codeDigits` is negative.
2220
*/
23-
open class HmacOneTimePasswordConfig(var codeDigits: Int, var hmacAlgorithm: HmacAlgorithm) {
21+
open class HmacOneTimePasswordConfig(val codeDigits: Int, val hmacAlgorithm: HmacAlgorithm) {
2422
// -- Companion Object -------------------------------------------------------------------------------------------- //
2523
// -- Properties -------------------------------------------------------------------------------------------------- //
2624
// -- Initialization ---------------------------------------------------------------------------------------------- //

src/main/kotlin/dev/turingcomplete/kotlinonetimepassword/HmacOneTimePasswordGenerator.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.turingcomplete.kotlinonetimepassword
22

3+
import org.apache.commons.codec.binary.Base32
34
import java.nio.ByteBuffer
45
import javax.crypto.Mac
56
import javax.crypto.spec.SecretKeySpec
@@ -89,7 +90,7 @@ open class HmacOneTimePasswordGenerator(private val secret: ByteArray,
8990
val codeInt = binary.int.rem(10.0.pow(config.codeDigits).toInt())
9091

9192
// The integer code variable may contain a value with fewer digits than the
92-
// required code digits. Therefore the final code value is filled with zeros
93+
// required code digits. Therefore, the final code value is filled with zeros
9394
// on the left, till the code digits requirement is fulfilled.
9495
//
9596
// Ongoing example:
@@ -109,6 +110,16 @@ open class HmacOneTimePasswordGenerator(private val secret: ByteArray,
109110
return code == generate(counter)
110111
}
111112

113+
/**
114+
* Creates an [OtpAuthUriBuilder], which pre-configured with the secret, as
115+
* well as the algorithm and code digits from the [config].
116+
*/
117+
fun otpAuthUriBuilder(initialCounter: Long): OtpAuthUriBuilder.Hotp {
118+
return OtpAuthUriBuilder.forHotp(initialCounter, Base32().encode(secret))
119+
.algorithm(config.hmacAlgorithm)
120+
.digits(config.codeDigits)
121+
}
122+
112123
// -- Private Methods --------------------------------------------------------------------------------------------- //
113124
// -- Inner Type -------------------------------------------------------------------------------------------------- //
114125
}

0 commit comments

Comments
 (0)