All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.io.github.landrynorris.otp.Totp.kt Maven / Gradle / Ivy

package io.github.landrynorris.otp

import kotlinx.datetime.Clock

/**
 * Implementation of RFC 6238 Time-Based One-Time Password.
 * Uses current time step as the challenge.
 * The time step is calculated as the Unix timestamp divided by [timeStep].
 * The timestamp can be overridden using [setTime].
 */
data class Totp(override val secret: ByteArray, override val name: String,
                private val timeStep: Int = 30, override val codeLength: Int = 6):
    Otp(secret, name, codeLength) {
    private var overwrittenTime: Long? = null
    private val clock = Clock.System

    /**
     * Same as primary constructor, but takes [secret] as a Base32 encoded String.
     */
    constructor(secret: String, name: String, timeStep: Int = 30, codeLength: Int = 6):
            this(Base32.decode(secret), name, timeStep, codeLength)

    /**
     * Gets the current time step as a challenge.
     * Time step is calculated as t/[timeStep], where
     * t is the Unix timestamp in seconds.
     */
    override fun getValue(): ByteArray {
        return t.toBytes()
    }

    /**
     * Override the timestamp. This will replace
     * the Unix timestamp in future pin calculations.
     */
    fun setTime(newTimeSeconds: Long) {
        overwrittenTime = newTimeSeconds
    }

    /**
     * Value in [0, 1)f describing the current fraction of the way through
     * the current time step.
     */
    val progress: Float get() {
        val time = getTimeMillis()
        val timeStepMs = timeStep*1000
        return (time % timeStepMs).toFloat() / timeStepMs
    }

    /**
     * Time step value. Equal to [getTime]/[timeStep]
     */
    private val t: Long get() {
        val time = getTime()
        return time/timeStep
    }

    /**
     * returns the time IN SECONDS to use in the calculation of the pin.
     */
    private fun getTime(): Long {
        return if(overwrittenTime != null) {
            overwrittenTime!!
        } else {
            clock.now().epochSeconds
        }
    }

    /**
     * Returns the time in ms to use in calculation of [progress]
     */
    private fun getTimeMillis(): Long {
        return if(overwrittenTime != null) {
            overwrittenTime!!*1000 //convert to ms
        } else {
            clock.now().toEpochMilliseconds()
        }
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other == null || this::class != other::class) return false

        other as Totp

        if (!secret.contentEquals(other.secret)) return false
        if (name != other.name) return false
        if (timeStep != other.timeStep) return false
        if (codeLength != other.codeLength) return false
        if (overwrittenTime != other.overwrittenTime) return false
        if (clock != other.clock) return false

        return true
    }

    override fun hashCode(): Int {
        var result = secret.contentHashCode()
        result = 31 * result + name.hashCode()
        result = 31 * result + timeStep
        result = 31 * result + codeLength
        result = 31 * result + (overwrittenTime?.hashCode() ?: 0)
        result = 31 * result + clock.hashCode()
        return result
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy