Created
March 27, 2020 20:10
-
-
Save diefferson/8ff22ef8d5a48714765c3c3e04f63c32 to your computer and use it in GitHub Desktop.
Kotlin OTP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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