Password Hashers

For password hashers on the JCA, you have three options: BCrypt, SCrypt and HardenedSCrypt (Which is basically scrypt but with much more secure parameters, but a lot slower).

SCrypt is recommended over BCrypt, as it improves over the memory-hardness of BCrypt. Over both, Argon2 from the libsodium package is preferred.

Password hashing involves nonce generation, thus, it uses java’s SecureRandom for the JCA, and libsodium’s own random function for the libsodium module. Thus, it is inherently side effecting.

Preferably, if possible, you want to receive your password as an Array[Byte] or Array[Char] without ever storing a string. TSec handles this case first and foremost.

This stack overflow link explains this quite clearly, but, in a nutshell, Strings cannot be wiped by anything other than GC, thus they will be in memory until then, which poses a vulnerability. The main problem is that this allows access to plaintext passwords if the attacker has direct access to memory, which is more dangerous than just accessing the hashes.

That said, it’s more of a precaution: If an attacker has direct memory access to your application, you most likely have much bigger problems to worry about (Compromised and vulnerable network/ports and things open to the internet). So while it may be grasping at straws a bit, A security library should aim for the most secure default.

So for the default case of char arrays, we can hash into any Sync[F] as such:

  import cats.effect.IO
  import tsec.passwordhashers._
  import tsec.passwordhashers.jca._
  val pass: Array[Char] = Array('h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd')
  val bestbcryptHash: IO[PasswordHash[BCrypt]]                 = BCrypt.hashpw[IO](pass)
  val bestscryptHash: IO[PasswordHash[SCrypt]]                 = SCrypt.hashpw[IO](pass)
  val besthardenedScryptHash: IO[PasswordHash[HardenedSCrypt]] = HardenedSCrypt.hashpw[IO](pass)

The string case is the same.

  val bcryptHash: IO[PasswordHash[BCrypt]]                 = BCrypt.hashpw[IO]("hiThere")
  val scryptHash: IO[PasswordHash[SCrypt]]                 = SCrypt.hashpw[IO]("hiThere")
  val hardenedScryptHash: IO[PasswordHash[HardenedSCrypt]] = HardenedSCrypt.hashpw[IO]("hiThere")

To Validate, you can check against a hash! Naturally, if it returns false, it was hashed incorrectly.

  val checkProgram: IO[Boolean] = for {
    hash  <- bcryptHash
    check <- BCrypt.checkpwBool[IO]("hiThere", hash)
  } yield check

Alternatively, if purity is your enemy, you can use the unsafe methods. Do note: these may throw an exception for malformed input when checking password, and in hashPwUnsafe, we generate a nonce using SecureRandom, thus it is side effecting.

  val unsafeHash: PasswordHash[BCrypt] = BCrypt.hashpwUnsafe("hiThere")
  val unsafeCheck: Boolean             = BCrypt.checkpwUnsafe("hiThere", unsafeHash)