argon2 works
This commit is contained in:
parent
447ebfd255
commit
a8ba253ea1
2 changed files with 96 additions and 1 deletions
|
|
@ -1,6 +1,7 @@
|
|||
package gs.smolban.auth
|
||||
|
||||
import java.util.Base64
|
||||
import java.util.Objects
|
||||
import scala.util.Try
|
||||
|
||||
/** Represents an Argon2 hash packed with the parameters that produced it.
|
||||
|
|
@ -30,6 +31,30 @@ final class Argon2Hash(
|
|||
val hash: Array[Byte]
|
||||
):
|
||||
|
||||
override def equals(obj: Any): Boolean =
|
||||
obj match
|
||||
case other: Argon2Hash =>
|
||||
(algorithmVersion == other.algorithmVersion)
|
||||
&& (algorithmType == other.algorithmType)
|
||||
&& (iterations == other.iterations)
|
||||
&& (parallelism == other.parallelism)
|
||||
&& (memoryInKb == other.memoryInKb)
|
||||
&& (salt.sameElements(other.salt))
|
||||
&& (hash.sameElements(other.hash))
|
||||
|
||||
override def hashCode(): Int =
|
||||
Objects.hash(
|
||||
algorithmVersion,
|
||||
algorithmType,
|
||||
iterations,
|
||||
parallelism,
|
||||
memoryInKb,
|
||||
salt,
|
||||
hash
|
||||
)
|
||||
|
||||
override def toString(): String = encode()
|
||||
|
||||
/** Encode this hash as a '$' delimited string that includes all parameters.
|
||||
* This string can be parsed by using the `decode` function.
|
||||
*
|
||||
|
|
@ -37,7 +62,7 @@ final class Argon2Hash(
|
|||
* The encoded hash string.
|
||||
*/
|
||||
def encode(): String =
|
||||
s"v=$algorithmVersion$$t=$algorithmType$$i=$iterations$$p=$parallelism$$m=$memoryInKb}$$${encodedSalt()}$$${encodedHash()}"
|
||||
s"v=$algorithmVersion$$t=$algorithmType$$i=$iterations$$p=$parallelism$$m=$memoryInKb$$${encodedSalt()}$$${encodedHash()}"
|
||||
|
||||
private def encodedSalt(): String =
|
||||
Base64.getEncoder().encodeToString(salt)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
package gs.smolban.auth
|
||||
|
||||
import cats.effect.IO
|
||||
import cats.effect.unsafe.IORuntime
|
||||
import munit.Location
|
||||
import org.bouncycastle.crypto.params.Argon2Parameters
|
||||
|
||||
class Argon2Tests extends munit.FunSuite:
|
||||
given IORuntime = IORuntime.global
|
||||
|
||||
def iotest(
|
||||
name: String
|
||||
)(
|
||||
f: => IO[Unit]
|
||||
)(
|
||||
using
|
||||
Location
|
||||
): Unit =
|
||||
test(name)(f.unsafeRunSync())
|
||||
|
||||
val rng: RandomByteProvider[IO] = RandomByteProvider.secureRandom[IO]
|
||||
|
||||
val altConfig: Argon2.Config = Argon2.Config(
|
||||
algorithmVersion = Argon2Parameters.ARGON2_VERSION_10,
|
||||
algorithmType = Argon2Parameters.ARGON2_i,
|
||||
iterations = 2,
|
||||
parallelism = 1,
|
||||
memoryInKb = 1024,
|
||||
saltLengthInBytes = 8,
|
||||
hashLengthInBytes = 64
|
||||
)
|
||||
|
||||
iotest("should calculate a hash and verify against that hash") {
|
||||
val input = "some Complex password!1"
|
||||
for
|
||||
secret <- Argon2Secret.generate(32, rng)
|
||||
argon2 <- IO(new Argon2[IO](Argon2.defaultConfig(), secret, rng))
|
||||
hash <- argon2.calculateHash(input)
|
||||
matched <- argon2.doesInputMatch(input, hash)
|
||||
encoded <- IO(hash.encode())
|
||||
decoded <- IO(Argon2Hash.decode(encoded))
|
||||
yield
|
||||
assertEquals(matched, true)
|
||||
assertEquals(hash.algorithmType, Argon2.Defaults.AlgorithmType)
|
||||
assertEquals(hash.algorithmVersion, Argon2.Defaults.AlgorithmVersion)
|
||||
assertEquals(hash.iterations, Argon2.Defaults.Iterations)
|
||||
assertEquals(hash.parallelism, Argon2.Defaults.Parallelism)
|
||||
assertEquals(hash.memoryInKb, Argon2.Defaults.MemoryInKB)
|
||||
assertEquals(Some(hash), decoded)
|
||||
}
|
||||
|
||||
iotest("should match using stored params, not configured params") {
|
||||
val input = "Another super s3cr3t pass@"
|
||||
for
|
||||
secret <- Argon2Secret.generate(32, rng)
|
||||
altArgon2 <- IO(new Argon2[IO](altConfig, secret, rng))
|
||||
defArgon2 <- IO(new Argon2[IO](Argon2.defaultConfig(), secret, rng))
|
||||
altHash <- altArgon2.calculateHash(input)
|
||||
// We're using the default configuration to run the match, and it should
|
||||
// still match because we have the same secret and the params are
|
||||
// extracted from the hash rather than the configuration.
|
||||
matched <- defArgon2.doesInputMatch(input, altHash)
|
||||
yield
|
||||
assertEquals(matched, true)
|
||||
assertEquals(altHash.algorithmType, altConfig.algorithmType)
|
||||
assertEquals(altHash.algorithmVersion, altConfig.algorithmVersion)
|
||||
assertEquals(altHash.iterations, altConfig.iterations)
|
||||
assertEquals(altHash.parallelism, altConfig.parallelism)
|
||||
assertEquals(altHash.memoryInKb, altConfig.memoryInKb)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue