Skip to content

Commit

Permalink
Merge pull request #1 from hairylime/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
hairylime authored Sep 15, 2017
2 parents 43ba9dc + 143cd62 commit 9acc1da
Show file tree
Hide file tree
Showing 11 changed files with 71 additions and 59 deletions.
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ scalacOptions in Compile in doc ++= Seq(
"-sourcepath", (baseDirectory in LocalProject("kuro-otp")).value.getAbsolutePath,
"-doc-title", "Kuro OTP (HOTP, TOTP)",
"-doc-footer", "Copyright (c) 2017 Ryo Ejima (ejisan), Apache License v2.0.",
"-doc-source-url", "https://github.com/ejisan/kuro-otp€{FILE_PATH}.scala")
"-doc-source-url", "https://github.com/ejisan/kuro-otp/tree/master€{FILE_PATH}.scala")

javacOptions ++= Seq("-source", "1.8")

Expand All @@ -51,6 +51,7 @@ testOptions in Test ++= Seq(
Tests.Argument(TestFrameworks.JUnit, "-q", "-v"))

libraryDependencies ++= Seq(
"com.typesafe" % "config" % "1.3.1",
"org.scala-lang.modules" %% "scala-java8-compat" % "0.8.0",
"commons-codec" % "commons-codec" % "1.10",
"junit" % "junit" % "4.12" % Test,
Expand Down
14 changes: 14 additions & 0 deletions src/main/resources/reference.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
kuro {
random {
algorithm = "NativePRNGNonBlocking"
provider = "SUN"
}

hmac {
provider = "SunJCE"
}

keyGenerator {
provider = "SunJCE"
}
}
2 changes: 1 addition & 1 deletion src/main/scala/ejisan/kuro/otp/HOTP.scala
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class HOTP(
* }
* val code2 = hotp.generate(3l)
* hotp.validate(0l, 5, code2) foreach { gap =>
* println(s"You are authenticated! (gap: $gap)")
* println(s"You are authenticated! (gap: " + gap + ")")
* }
* }}}
* === Java ===
Expand Down
20 changes: 18 additions & 2 deletions src/main/scala/ejisan/kuro/otp/HOTPSupport.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package ejisan.kuro.otp

import com.typesafe.config.{ Config, ConfigFactory }

/**
* An HMAC-Based One-Time Password Algorithm (HOTP) Implementation.
*
Expand Down Expand Up @@ -44,6 +46,11 @@ package ejisan.kuro.otp
* }}}
*
* @see [[https://tools.ietf.org/html/rfc4226 RFC 4226]]
*
* @note
* This class uses default Mac provider as `SunJCE`,
* and this can be changed by configuring the key `kuro.hmac.provider`
* in reference.conf or application.conf.
*/
trait HOTPSupport {
/**
Expand All @@ -66,9 +73,16 @@ trait HOTPSupport {
(truncatedHash % scala.math.pow(10, digits.toDouble)).toInt
}

/** Configuration */
protected val config: Config = ConfigFactory.load()

/**
* Calculates a HMAC value.
* This method uses the SunJCE provider to provide the HMAC algorithm.
*
* @note
* This method uses default Mac provider as `SunJCE`,
* and this can be changed by configuring the key `kuro.hmac.provider`
* in reference.conf or application.conf.
*
* @see [[https://tools.ietf.org/html/rfc4226#section-5.3 Generating an HOTP Value]]
*
Expand All @@ -78,7 +92,9 @@ trait HOTPSupport {
* @return A HMAC value.
*/
protected def hmac(algorithm: OTPAlgorithm, otpkey: OTPKey, input: Array[Byte]): Array[Byte] = {
val mac = javax.crypto.Mac.getInstance(algorithm.value, "SunJCE")
val mac = javax.crypto.Mac.getInstance(
algorithm.value,
Option(config.getString("kuro.hmac.provider")).getOrElse("SunJCE"))
mac.init(otpkey.get)
mac.doFinal(input)
}
Expand Down
7 changes: 0 additions & 7 deletions src/main/scala/ejisan/kuro/otp/OTPAlgorithm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,10 @@ sealed class OTPAlgorithm(
object OTPAlgorithm {
import scala.compat.java8.OptionConverters._

case object MD5 extends OTPAlgorithm("MD5", "HmacMD5", 160, 160)
case object SHA1 extends OTPAlgorithm("SHA1", "HmacSHA1", 160, 200)
case object SHA256 extends OTPAlgorithm("SHA256", "HmacSHA256", 240, 280)
case object SHA512 extends OTPAlgorithm("SHA512", "HmacSHA512", 480, 520)

/**
* JAVA API: Returns MD5 algorithm.
*/
def getMD5(): OTPAlgorithm = MD5

/**
* JAVA API: Returns SHA1 algorithm.
*/
Expand All @@ -66,7 +60,6 @@ object OTPAlgorithm {
*/
def find(name: String): Option[OTPAlgorithm] = {
name match {
case "MD5" => Some(MD5)
case "SHA1" => Some(SHA1)
case "SHA256" => Some(SHA256)
case "SHA512" => Some(SHA512)
Expand Down
66 changes: 33 additions & 33 deletions src/main/scala/ejisan/kuro/otp/OTPKey.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ejisan.kuro.otp

import java.util.{ Arrays, Base64 }
import java.security.{ Key, SecureRandom }
import com.typesafe.config.{ Config, ConfigFactory }
import org.apache.commons.codec.binary.{ Base32, Hex }

/**
Expand Down Expand Up @@ -126,6 +127,9 @@ class OTPKey private (key: Key) {
* }}}
*/
object OTPKey {
/** Configuration */
protected val config: Config = ConfigFactory.load()

/**
* Creates new [[OTPKey]] instance.
*
Expand Down Expand Up @@ -207,60 +211,56 @@ object OTPKey {
def fromBase32Hex(base32Hex: String, strict: Boolean = true): OTPKey =
fromByteArray((new Base32(true)).decode(base32Hex), strict)

@inline
private def defaultPRNG: SecureRandom =
SecureRandom.getInstance("NativePRNGNonBlocking", "SUN")

/**
* Generates random [[OTPKey]] instance.
*
* @note
* This method uses default generation provider as `SunJCE`,
* and this can be changed by configuring the key `kuro.keyGenerator.provider`
* in reference.conf or application.conf.
*
* @param keyLength the key length
* @param algorithm the algorithm
* @param algorithm the flavor of key generation
* @param prng the random number generator
*/
def random(keyLength: Int, algorithm: OTPAlgorithm, strict: Boolean, prng: SecureRandom): OTPKey = {
val gen = javax.crypto.KeyGenerator.getInstance(algorithm.value)
val gen = javax.crypto.KeyGenerator.getInstance(
algorithm.value,
Option(config.getString("kuro.keyGenerator.provider")).getOrElse("SunJCE"))
gen.init(keyLength, prng)
apply(gen.generateKey, strict)
}

/**
* Generates random [[OTPKey]] instance.
*
* @param keyLength the key length
* @param algorithm the algorithm
* @param strict
*/
def random(keyLength: Int, algorithm: OTPAlgorithm, strict: Boolean = true): OTPKey =
random(keyLength, algorithm, strict, defaultPRNG)
@inline
private def defaultPRNG: SecureRandom = {
SecureRandom.getInstance(
Option(config.getString("kuro.random.algorithm")).getOrElse("NativePRNGNonBlocking"),
Option(config.getString("kuro.random.provider")).getOrElse("SUN"))
}

/**
* Generates random [[OTPKey]] instance with default key length.
*
* @param algorithm the algorithm
* @param prng the random number generator
*/
def random(algorithm: OTPAlgorithm, prng: SecureRandom): OTPKey =
random(algorithm.defaultKeyLength, algorithm, false, prng)

/**
* Generates random [[OTPKey]] instance with default key length.
* @note
* This method uses default algorithm as `NativePRNGNonBlocking` from `Sun` provider,
* and this can be changed by configuring the key `kuro.random.algorithm` and
* `kuro.random.provider` in reference.conf or application.conf.
*
* @param algorithm the flavor of key generation
*/
def random(algorithm: OTPAlgorithm): OTPKey =
random(algorithm, defaultPRNG)
random(algorithm.defaultKeyLength, algorithm, true, defaultPRNG)

/**
* Generates random [[OTPKey]] instance with stronger key length.
*
* @param algorithm the algorithm
* @param prng the random number generator
*/
def randomStrong(algorithm: OTPAlgorithm, prng: SecureRandom): OTPKey =
random(algorithm.strongKeyLength, algorithm, false, prng)

/**
* Generates random [[OTPKey]] instance with stronger key length.
* @note
* This method uses default algorithm as `NativePRNGNonBlocking` from `Sun` provider,
* and this can be changed by configuring the key `kuro.random.algorithm` and
* `kuro.random.provider` in reference.conf or application.conf.
*
* @param algorithm the flavor of key generation
*/
def randomStrong(algorithm: OTPAlgorithm): OTPKey =
randomStrong(algorithm, defaultPRNG)
random(algorithm.strongKeyLength, algorithm, true, defaultPRNG)
}
6 changes: 3 additions & 3 deletions src/main/scala/ejisan/kuro/otp/TOTP.scala
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +154,15 @@ class TOTP(
/**
* Java API: Validates the given TOTP code for the instant of time.
*
* @see [[TOTP.validate(window:Int,code:String):java.util.OptionalLong* TOTP.validate]]
* @see [[TOTP.validate(window:Int,code:String):Option[Long]* TOTP.validate]]
*/
def validateAsJava(window: Int, code: String): java.util.OptionalLong =
validateAsJava(currentTime(), window, code)

/**
* Java API: Validates the given TOTP code for the instant of time.
*
* @see [[TOTP.validate(instantTimestamp:Long,window:Int,code:String):java.util.OptionalLong* TOTP.validate]]
* @see [[TOTP.validate(instantTimestamp:Long,window:Int,code:String):Option[Long]* TOTP.validate]]
*/
def validateAsJava(instantTimestamp: Long, window: Int, code: String): java.util.OptionalLong =
validate(instantTimestamp, window, code).asPrimitive
Expand Down Expand Up @@ -240,7 +240,7 @@ class TOTP(
* }
* val code2 = totp.generate(3l)
* totp.validate(5, code2) foreach { gap =>
* println(s"You are authenticated! (gap: $gap)")
* println(s"You are authenticated! (gap: " + gap + ")")
* }
* }}}
* === Java ===
Expand Down
2 changes: 0 additions & 2 deletions src/test/java/ejisan/kuro/otp/OTPAlgorithmTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ public class OTPAlgorithmTest {

@Test
public void testAlgorithmValue() throws Exception {
assertThat(OTPAlgorithm.getMD5().value(), is ("HmacMD5"));
assertThat(OTPAlgorithm.getSHA1().value(), is ("HmacSHA1"));
assertThat(OTPAlgorithm.getSHA256().value(), is ("HmacSHA256"));
assertThat(OTPAlgorithm.getSHA512().value(), is ("HmacSHA512"));
}

@Test
public void testAlgorithmName() throws Exception {
assertThat(OTPAlgorithm.getMD5().name(), is ("MD5"));
assertThat(OTPAlgorithm.getSHA1().name(), is ("SHA1"));
assertThat(OTPAlgorithm.getSHA256().name(), is ("SHA256"));
assertThat(OTPAlgorithm.getSHA512().name(), is ("SHA512"));
Expand Down
4 changes: 0 additions & 4 deletions src/test/java/ejisan/kuro/otp/OTPKeyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,6 @@ public void testBase32Hex() throws Exception {

@Test
public void testRandom() throws Exception {
OTPKey md5Key = OTPKey.random(OTPAlgorithm.getMD5());
assertThat(md5Key.keyLength(), is (OTPAlgorithm.getMD5().defaultKeyLength()));
OTPKey sha1Key = OTPKey.random(OTPAlgorithm.getSHA1());
assertThat(sha1Key.keyLength(), is (OTPAlgorithm.getSHA1().defaultKeyLength()));
OTPKey sha256Key = OTPKey.random(OTPAlgorithm.getSHA256());
Expand All @@ -86,8 +84,6 @@ public void testRandom() throws Exception {

@Test
public void testRandomStrong() throws Exception {
OTPKey md5Key = OTPKey.randomStrong(OTPAlgorithm.getMD5());
assertThat(md5Key.keyLength(), is (OTPAlgorithm.getMD5().strongKeyLength()));
OTPKey sha1Key = OTPKey.randomStrong(OTPAlgorithm.getSHA1());
assertThat(sha1Key.keyLength(), is (OTPAlgorithm.getSHA1().strongKeyLength()));
OTPKey sha256Key = OTPKey.randomStrong(OTPAlgorithm.getSHA256());
Expand Down
2 changes: 0 additions & 2 deletions src/test/scala/ejisan/kuro/otp/OTPAlgorithmSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ class OTPAlgorithmSpec extends FlatSpec with Matchers {

// https://github.com/google/google-authenticator/wiki/Key-Uri-Format
"OTPAlgorithm#name" should "return algorithm value for otpauth URI" in {
OTPAlgorithm.MD5.name should be ("MD5")
OTPAlgorithm.SHA1.name should be ("SHA1")
OTPAlgorithm.SHA256.name should be ("SHA256")
OTPAlgorithm.SHA512.name should be ("SHA512")
}

"OTPAlgorithm#value" should "return algorithm value for Java [[javax.crypto.Mac]]" in {
OTPAlgorithm.MD5.value should be ("HmacMD5")
OTPAlgorithm.SHA1.value should be ("HmacSHA1")
OTPAlgorithm.SHA256.value should be ("HmacSHA256")
OTPAlgorithm.SHA512.value should be ("HmacSHA512")
Expand Down
4 changes: 0 additions & 4 deletions src/test/scala/ejisan/kuro/otp/OTPKeySpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ class OTPKeySpec extends FlatSpec with Matchers {
}

"OTPKey#random" should "generate key length of default length" in {
val md5Key = OTPKey.random(OTPAlgorithm.MD5)
md5Key.keyLength should be (OTPAlgorithm.MD5.defaultKeyLength)
val sha1Key = OTPKey.random(OTPAlgorithm.SHA1)
sha1Key.keyLength should be (OTPAlgorithm.SHA1.defaultKeyLength)
val sha256Key = OTPKey.random(OTPAlgorithm.SHA256)
Expand All @@ -74,8 +72,6 @@ class OTPKeySpec extends FlatSpec with Matchers {
}

"OTPKey#randomStrong" should "generate key length of default strong length" in {
val md5Key = OTPKey.randomStrong(OTPAlgorithm.MD5)
md5Key.keyLength should be (OTPAlgorithm.MD5.strongKeyLength)
val sha1Key = OTPKey.randomStrong(OTPAlgorithm.SHA1)
sha1Key.keyLength should be (OTPAlgorithm.SHA1.strongKeyLength)
val sha256Key = OTPKey.randomStrong(OTPAlgorithm.SHA256)
Expand Down

0 comments on commit 9acc1da

Please sign in to comment.