From b2cd92ad941df169c2c5875bb5445128c0d0bb14 Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Sat, 5 May 2018 15:26:16 +0200 Subject: [PATCH] implement sha1 based totp --- modules/user/src/main/TotpSecret.scala | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/modules/user/src/main/TotpSecret.scala b/modules/user/src/main/TotpSecret.scala index 6ec4b78b14..1efca7e49b 100644 --- a/modules/user/src/main/TotpSecret.scala +++ b/modules/user/src/main/TotpSecret.scala @@ -1,7 +1,9 @@ package lila.user import java.security.SecureRandom -import scala.math.BigInt +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec +import scala.math.{ pow, BigInt } case class TotpSecret(val secret: Array[Byte]) { override def toString = "TotpSecret(****************)" @@ -9,9 +11,31 @@ case class TotpSecret(val secret: Array[Byte]) { def base32: String = { new String(BigInt(secret).toString.toCharArray.map(_.asDigit).map(TotpSecret.base(_))) } + + def totp(period: Long): String = { + // Loosely based on scala-totp-auth: + // https://github.com/marklister/scala-totp-auth/blob/master/src/main/scala/Authenticator.scala + + val msg = BigInt(period).toByteArray.reverse.padTo(8, 0.toByte).reverse + + val hmac = Mac.getInstance("HmacSha1") + hmac.init(new SecretKeySpec(secret, "RAW")) + val hash = hmac.doFinal(msg) + + val offset = hash(hash.length - 1) & 0xf + + val binary: Long = ((hash(offset) & 0x7f) << 24 | + (hash(offset + 1) & 0xff) << 16 | + (hash(offset + 2) & 0xff) << 8 | + (hash(offset + 3) & 0xff)) + + val otp = binary % pow(10, TotpSecret.digits).toLong + ("0" * TotpSecret.digits + otp.toString).takeRight(TotpSecret.digits) + } } object TotpSecret { + private val digits = 6 private val base = ('A' to 'Z') ++ ('2' to '7') def apply(base32: String) = new TotpSecret(base32.map(base.indexOf(_)).foldLeft(0: BigInt)((a, b) => a * 32 + b).toByteArray)