kotlin-onetimepassword

repository·main·Indexed 41 days ago

https://github.com/marcelkliemannel/kotlin-onetimepassword

A Kotlin library for generating one-time password (OTP) codes compatible with Google Authenticator, TOTP (RFC 6238), and HOTP (RFC 4226) standards. Version 3.0.0 provides implementations for HmacOneTimePasswordGenerator and TimeBasedOneTimePasswordGenerator, supporting SHA1, SHA256, and SHA512 algorithms. Includes a GoogleAuthenticator wrapper for simplified compatibility and OtpAuthUriBuilder for generating QR code URIs.

Tokens
3.6K
Snippets
13
Records
14
Agent score
50%

What's inside kotlin-onetimepassword

  1. How One-Time Password (OTP) flows work

    main

    The OTP process relies on a shared secret between a user and a server.

    1. Shared Secret: Both parties agree on a stable secret in advance.
    2. Challenge: The server may send a challenge (e.g., a timestamp for TOTP) that the user must solve using the secret.
    3. Code: The user's client generates a numeric code (the OTP) which is sent to the server for validation.

    Implementation Note: The client and server must use identical configurations, specifically the number of code digits and the HMAC algorithm (e.g., SHA1, SHA256, SHA512).

    Code Digits: Codes are returned as String to preserve leading zeroes. While RFC 4226 recommends 6-8 digits, this library allows explicit configuration for shorter lengths if allowInsecureConfiguration = true is set.

  2. Install the Kotlin One-Time Password library

    main

    Add the library as a dependency to your project using Gradle or Maven. The library is hosted on Maven Central.

    Version: 3.0.0

    // Groovy
    implementation 'dev.turingcomplete:kotlin-onetimepassword:3.0.0'
    // Kotlin
    implementation("dev.turingcomplete:kotlin-onetimepassword:3.0.0")
    <!-- Maven -->
    <dependency>
        <groupId>dev.turingcomplete</groupId>
        <artifactId>kotlin-onetimepassword</artifactId>
        <version>3.0.0</version>
    </dependency>
  3. Use TimeBasedOneTimePasswordGenerator (TOTP)

    main

    The TimeBasedOneTimePasswordGenerator implements the Time-based One-time Password (TOTP) algorithm. It requires a shared secret and a TimeBasedOneTimePasswordConfig.

    Configuration: Defines codeDigits, hmacAlgorithm, timeStep (the validity window), and timeStepUnit (e.g., TimeUnit.SECONDS).

    Generation: You can generate codes using a Long timestamp, a java.util.Date, or a java.time.Instant. If no argument is provided, the current system time is used.

    Validation: Use isValid(code: String, timestamp: Date) to validate a code against a specific time.

    Time Slot Management: Use counter() to get the current time slot and timeslotStart(counter) to calculate the epoch millisecond boundaries of a specific window.

    val secret = "Leia"
    val config = TimeBasedOneTimePasswordConfig(codeDigits = 8, 
                                                hmacAlgorithm = HmacAlgorithm.SHA1,
                                                timeStep = 30, 
                                                timeStepUnit = TimeUnit.SECONDS)
    val totpGenerator = TimeBasedOneTimePasswordGenerator(secret.toByteArray(), config)
    
    // Generate codes
    var code0: String = totpGenerator.generate() // Uses System.currentTimeMillis()
    var code1: String = totpGenerator.generate(timestamp = 1622234248000L)
    var code2: String = totpGenerator.generate(date = java.util.Date(59))
    
    // Validate a code
    val isValid = totpGenerator.isValid("12345678", timestamp = java.util.Date())
    
    // Calculate time slot boundaries
    val counter = totpGenerator.counter()
    val startEpochMillis = totpGenerator.timeslotStart(counter)
    val endEpochMillis = totpGenerator.timeslotStart(counter + 1) - 1
  4. Use HmacOneTimePasswordGenerator (HOTP)

    main

    The HmacOneTimePasswordGenerator implements the HMAC-based One-time Password (HOTP) algorithm. It requires a shared secret as a ByteArray and an HmacOneTimePasswordConfig object.

    Supported HMAC algorithms: SHA1, SHA256, and SHA512.

    To generate a code, use generate(counter: Long). To validate a code, use isValid(code: String, counter: Long).

    val secret = "Leia"
    val config = HmacOneTimePasswordConfig(codeDigits = 8, 
                                           hmacAlgorithm = HmacAlgorithm.SHA1)
    val hmacOneTimePasswordGenerator = HmacOneTimePasswordGenerator(secret.toByteArray(), config)
    
    // Generate codes using an incrementing counter
    var code0: String = hmacOneTimePasswordGenerator.generate(counter = 0)
    var code1: String = hmacOneTimePasswordGenerator.generate(counter = 1)
    
    // Validate a code
    val isValid = hmacOneTimePasswordGenerator.isValid("12345678", counter = 1)
  5. Generate Key URI Format for QR Codes

    main

    To facilitate easy account setup via QR codes, use OtpAuthUriBuilder to create a URI following the Key Uri Format specification.

    Note: The specification requires that Base32 padding characters (=) are removed from the secret parameter in the URI.

    All generators provide an otpAuthUriBuilder() helper that pre-configures the builder with the generator's current settings.

    // Manual construction
    OtpAuthUriBuilder.forTotp(Base32().encode("secret".toByteArray()))
      .label("John", "Company")
      .issuer("Company")
      .digits(8)
      .buildToString()
    
    // Using the generator's helper (pre-configured)
    val googleAuth = GoogleAuthenticator(Base32().encode("secret".toByteArray()))
    val uri = googleAuth.otpAuthUriBuilder()
      .issuer("Company")
      .buildToString()
  6. Generate random secrets with RandomSecretGenerator

    main

    Use RandomSecretGenerator to create cryptographically secure shared secrets. It is recommended to use a secret length that matches the output of the chosen HMAC algorithm (e.g., 20 bytes for SHA1).

    val randomSecretGenerator = RandomSecretGenerator()
    
    val secretSHA1 = randomSecretGenerator.createRandomSecret(HmacAlgorithm.SHA1)   // 20-byte
    val secretSHA256 = randomSecretGenerator.createRandomSecret(HmacAlgorithm.SHA256) // 32-byte
    val secretSHA512 = randomSecretGenerator.createRandomSecret(HmacAlgorithm.SHA512) // 64-byte
  7. Use GoogleAuthenticator for compatibility

    main

    The GoogleAuthenticator class provides a simplified wrapper for TOTP that is compatible with the Google Authenticator app. It uses a fixed 6-digit code, SHA1 algorithm, and a 30-second time window.

    Crucial Requirement: The constructor expects the Base32-encoded version of the secret, not the plain-text bytes.

    Secret Generation:

    • createRandomSecretAsByteArray(): Returns a 16-character Base32-encoded secret (10-byte plain-text). This follows the historical Google convention.
    • createSecureRandomSecretAsByteArray(): Returns a 20-byte plain-text secret (160-bit) encoded in Base32. Recommended for new deployments.
    // Using a plain-text secret with Base32 encoding
    val plainTextSecret = "Secret1234".toByteArray(Charsets.UTF_8)
    val base32EncodedSecret = Base32().encodeToString(plainTextSecret)
    
    val googleAuthenticator = GoogleAuthenticator(base32EncodedSecret)
    var code = googleAuthenticator.generate()
    
    // Or generate a new compatible secret
    val newSecret = GoogleAuthenticator.createRandomSecretAsByteArray()
    val ga = GoogleAuthenticator(newSecret)
  8. Use GoogleAuthenticator to generate and validate TOTP codes

    main

    The GoogleAuthenticator class is a convenience wrapper for generating Time-based One-Time Passwords (TOTP) that are compatible with Google Authenticator. It uses a fixed configuration:

    • HMAC algorithm: HmacAlgorithm.SHA1
    • Time step: 30 seconds
    • Code digits: 6

    To use it, provide a Base32-encoded secret as a ByteArray. You can then generate codes for a specific timestamp or validate a provided code against a timestamp.

    // Initialize with a Base32-encoded secret
    val secret = GoogleAuthenticator.createSecureRandomSecretAsByteArray()
    val authenticator = GoogleAuthenticator(secret)
    
    // Generate a code (defaults to current system time)
    val code = authenticator.generate()
    
    // Validate a code
    val isValid = authenticator.isValid(code)
  9. Create an OTP Auth URI for QR codes

    main

    To allow users to easily add an account to an authenticator app, you can generate an otpauth:// URI. The GoogleAuthenticator class provides a preconfigured builder that uses the correct secret and the fixed Google Authenticator settings (SHA1, 30s step, 6 digits).

    val authenticator = GoogleAuthenticator(secret)
    val uriBuilder = authenticator.otpAuthUriBuilder()
    // Use uriBuilder to construct your URI
  10. Generate random secrets with RandomSecretGenerator

    main

    The RandomSecretGenerator class provides utilities for generating cryptographically secure random bytes using SecureRandom. These bytes serve as the shared secrets required for OTP (HOTP/TOTP) configurations.

    Important Note on Encoding: The generated secret is a raw ByteArray. Most authenticator apps (like Google Authenticator) and OTP Auth URIs expect the secret to be encoded in Base32. You must encode these bytes before using them in a URI or providing them to a user as a text-based secret.

    val generator = RandomSecretGenerator()
    
    // Option 1: Generate a secret of a specific byte length
    val secret = generator.createRandomSecret(20)
    
    // Option 2: Generate a secret matching the length of an HMAC algorithm
    val secretFromAlgo = generator.createRandomSecret(HmacAlgorithm.SHA1)
  11. Generate random secrets with GoogleAuthenticator

    main

    The GoogleAuthenticator companion object provides methods to generate new secrets.

    • createSecureRandomSecretAsByteArray(): Generates an RFC 4226 recommended 160-bit secret as a Base32-encoded ByteArray. This is the preferred method for new deployments.
    • createRandomSecretAsByteArray(): Generates an 80-bit secret (16-character Base32) to maintain compatibility with historical Google Authenticator conventions.
    • createRandomSecret(): (Deprecated) Returns the 80-bit secret as a String. Use the ByteArray version instead to avoid keeping secrets in immutable JVM strings.
    // Recommended for new deployments
    val secureSecret = GoogleAuthenticator.createSecureRandomSecretAsByteArray()
    
    // For historical 80-bit compatibility
    val legacySecret = GoogleAuthenticator.createRandomSecretAsByteArray()