Skip to content

Instantly share code, notes, and snippets.

@diefferson
Created March 27, 2020 20:10
Show Gist options
  • Save diefferson/8ff22ef8d5a48714765c3c3e04f63c32 to your computer and use it in GitHub Desktop.
Save diefferson/8ff22ef8d5a48714765c3c3e04f63c32 to your computer and use it in GitHub Desktop.
Kotlin OTP
import java.nio.ByteBuffer
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
class Token @Throws(TokenUriInvalidException::class) constructor(secretText: String) {
private val algo: String = "sha1"
private val digits: Int = 6
private val period: Int = 30
var secret: ByteArray
var email:String = ""
init {
try {
secret = Base32String.decode(secretText)
} catch (e: Base32String.DecodingException) {
throw TokenUriInvalidException()
} catch (e: NullPointerException) {
throw TokenUriInvalidException()
}
}
private fun getHOTP(counter:Long):String {
// Encode counter in network byte order
val bb = ByteBuffer.allocate(8)
bb.putLong(counter)
var div = 1
for (i in digits downTo 1)
div *= 10
try {
val mac = Mac.getInstance("Hmac$algo")
mac.init(SecretKeySpec(secret, "Hmac$algo"))
// Do the hashing
val digest = mac.doFinal(bb.array())
// Truncate
var binary: Int
val off = digest[digest.size - 1].toInt() and 0xf
binary = digest[off].toInt() and 0x7f shl 0x18
binary = binary or (digest[off + 1].toInt() and 0xff shl 0x10)
binary = binary or (digest[off + 2].toInt() and 0xff shl 0x08)
binary = binary or (digest[off + 3].toInt() and 0xff)
var hotp: String
binary %= div
// Zero pad
hotp = binary.toString()
while (hotp.length != digits)
hotp = "0$hotp"
return hotp
}catch ( e: InvalidKeyException) {
e.printStackTrace()
} catch ( e:NoSuchAlgorithmException) {
e.printStackTrace()
}
return ""
}
// NOTE: This may change internal data. You MUST save the token immediately.
fun generateCodes(cur:Long): TokenCode? {
val counter = cur / 1000 / period.toLong()
return TokenCode(getHOTP(counter),
counter * period.toLong() * 1000,
(counter + 1) * period.toLong() * 1000,
TokenCode(getHOTP(counter + 1),
(counter + 1) * period.toLong() * 1000,
(counter + 2) * period.toLong() * 1000))
}
class TokenUriInvalidException : Exception() {
companion object {
private const val serialVersionUID = -1108624734612362345L
}
}
}
class TokenCode(private val mCode: String, private val mStart: Long, private val mUntil: Long) {
private var mNext: TokenCode? = null
fun getCurrentCode(cur:Long):String?{
val active = getActive(cur) ?: return null
return active.mCode
}
fun getTotalProgress(cur:Long):Int{
val total = last.mUntil - mStart
val state = total - (cur - mStart)
return (state * 1000 / total).toInt()
}
fun getCurrentProgress(cur:Long):Int{
val active = getActive(cur) ?: return 0
val total = active.mUntil - active.mStart
val state = total - (cur - active.mStart)
return (state * 1000 / total).toInt()
}
private val last: TokenCode
get() = if (mNext == null) this else this.mNext!!.last
constructor(prev: TokenCode,
code: String,
start: Long,
until: Long) : this(code, start, until) {
prev.mNext = this
}
constructor(code: String,
start: Long,
until: Long,
next: TokenCode) : this(code, start, until) {
mNext = next
}
private fun getActive(curTime: Long): TokenCode? {
if (curTime in mStart until mUntil)
return this
return if (mNext == null) null else this.mNext!!.getActive(curTime)
}
}
val token = Token("tokenSecret")
val tokenCode = token.generateCodes(currentTimeInMillis)
val currentCode = tokenCode?.getCurrentCode(currentTimeInMillis)?:""
val currentTokenTime = tokenCode?.getCurrentProgress(currentTimeInMillis)?:0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment