1+ package dev.turingcomplete.kotlinonetimepassword
2+
3+ import com.bastiaanjansen.otp.HMACAlgorithm
4+ import com.bastiaanjansen.otp.TOTP
5+ import com.j256.twofactorauth.TimeBasedOneTimePasswordUtil
6+ import org.apache.commons.codec.binary.Base32
7+ import org.junit.jupiter.api.Assertions
8+ import org.junit.jupiter.api.DisplayName
9+ import org.junit.jupiter.params.ParameterizedTest
10+ import org.junit.jupiter.params.provider.CsvSource
11+ import java.time.Duration
12+ import java.time.Instant
13+ import java.util.concurrent.TimeUnit
14+ import javax.crypto.spec.SecretKeySpec
15+
16+ class OtherLibrariesComparisonTest {
17+ // -- Companion Object -------------------------------------------------------------------------------------------- //
18+
19+ companion object {
20+ private val SECRET_PLAIN = " #ug0sEABk,}@anh&<ozWM6,#Nq/<NC3s"
21+ private val SECRET_BASE_32 = Base32 ().encodeToString(SECRET_PLAIN .toByteArray()).toString()
22+ }
23+
24+ // -- Properties -------------------------------------------------------------------------------------------------- //
25+ // -- Initialization ---------------------------------------------------------------------------------------------- //
26+ // -- Exposed Methods --------------------------------------------------------------------------------------------- //
27+
28+ @ParameterizedTest
29+ @DisplayName(" com.eatthepath:java-otp (https://github.com/jchambers/java-otp)" )
30+ @CsvSource(value = [" 15, 6" , " 15, 8" , " 30, 6" , " 30, 8" , " 45, 6" , " 45, 8" ])
31+ fun testComparetoEtthapath (timeStepSeconds : Long , digits : Int ) {
32+ val currentTime = System .currentTimeMillis()
33+ val expectedCode = createExpectedCode(timeStepSeconds, digits, currentTime)
34+
35+ // Library only supports 6-8 digits
36+ val eatthepath = com.eatthepath.otp.TimeBasedOneTimePasswordGenerator (Duration .ofSeconds(timeStepSeconds), digits, com.eatthepath.otp.TimeBasedOneTimePasswordGenerator .TOTP_ALGORITHM_HMAC_SHA1 )
37+ val eatthepathCode = eatthepath.generateOneTimePasswordString(SecretKeySpec (SECRET_PLAIN .toByteArray(), " RAW" ), Instant .ofEpochMilli(currentTime))
38+ Assertions .assertEquals(expectedCode, eatthepathCode)
39+ }
40+
41+ @ParameterizedTest
42+ @DisplayName(" com.github.bastiaanjansen:otp-java (https://github.com/BastiaanJansen/otp-java)" )
43+ @CsvSource(value = [" 15, 6" , " 15, 8" , " 30, 6" , " 30, 8" , " 45, 6" , " 45, 8" ])
44+ fun testCompareToBastiaanJansen (timeStepSeconds : Long , digits : Int ) {
45+ val currentTime = System .currentTimeMillis()
46+ val expectedCode = createExpectedCode(timeStepSeconds, digits, currentTime)
47+
48+ // Library only supports 6-8 digits
49+ val bastiaanjansen = TOTP .Builder (SECRET_BASE_32 .toByteArray()).withAlgorithm(HMACAlgorithm .SHA1 ).withPasswordLength(digits).withPeriod(Duration .ofSeconds(timeStepSeconds)).build()
50+ val bastiaanjansenCode = bastiaanjansen.at(Instant .ofEpochMilli(currentTime))
51+ Assertions .assertEquals(expectedCode, bastiaanjansenCode)
52+ }
53+
54+ @ParameterizedTest
55+ @DisplayName(" com.j256.two-factor-auth:two-factor-auth (https://github.com/j256/two-factor-auth)" )
56+ @CsvSource(value = [" 15, 6" , " 15, 8" , " 30, 6" , " 30, 8" , " 45, 6" , " 45, 8" ])
57+ fun testCompareToJ256 (timeStepSeconds : Long , digits : Int ) {
58+ val currentTime = System .currentTimeMillis()
59+ val expectedCode = createExpectedCode(timeStepSeconds, digits, currentTime)
60+
61+ val j256Code = TimeBasedOneTimePasswordUtil .generateNumberString(SECRET_BASE_32 , System .currentTimeMillis(), timeStepSeconds.toInt(), digits)
62+ Assertions .assertEquals(expectedCode, j256Code)
63+ }
64+
65+ // -- Private Methods --------------------------------------------------------------------------------------------- //
66+
67+ private fun createExpectedCode (timeStepSeconds : Long , digits : Int , currentTime : Long ): String {
68+ val config = TimeBasedOneTimePasswordConfig (timeStep = timeStepSeconds, timeStepUnit = TimeUnit .SECONDS , codeDigits = digits, hmacAlgorithm = HmacAlgorithm .SHA1 )
69+ val generator = TimeBasedOneTimePasswordGenerator (SECRET_PLAIN .toByteArray(), config)
70+ return generator.generate(currentTime)
71+ }
72+
73+ // -- Inner Type -------------------------------------------------------------------------------------------------- //
74+ }
0 commit comments