I'm a bit new to play framework and password hashing. I tried to find some solutions for hashing my passwords and I found BCrypt. Do you think that's good enough to hashing passwords. And if it's good, how can I get it working in the play framework? (I'm using play 2.1.3) Thanks!
Here's a sample Play Java project I wrote that uses BCrypt to hash passwords, see the newUser() and signIn() actions:
https://github.com/jroper/play-demo-twitbookplus/blob/master/app/controllers/UserController.java
You can do similar in Scala. To summarise, add jbycrpt to your dependencies in Build.scala:
val appDependencies = Seq(
"org.mindrot" % "jbcrypt" % "0.3m"
)
Then hash passwords using this:
String passwordHash = BCrypt.hashpw(password, BCrypt.gensalt());
And verify passwords using this:
BCrypt.checkpw(password, passwordHash)
Update (2020)
In my projects these days, I no longer use BCrypt, rather I use PBKDF2 hashes, which has the advantage of not requiring any additional dependencies, but the disadvantage of needing to write a fair bit more code and manually manage the salt. BCrypt also has some issues where different implementations are unable accurately to consume each others output, and some implementations even truncate long passwords, which is really bad. Even though it's a lot more code, I like this approach because it gives me more control, and it shows transparently exactly how things are working, and makes it easy to update things over time since the recommendations for hashing algorithms and input parameters continually change.
Anyway, here's the code I use, it stores the salt and the number of iterations used (so that these can be increased over time as best practices recommend) in the hashed value, separated by colons:
val DefaultIterations = 10000
val random = new SecureRandom()
private def pbkdf2(password: String, salt: Array[Byte], iterations: Int): Array[Byte] = {
val keySpec = new PBEKeySpec(password.toCharArray, salt, iterations, 256)
val keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
keyFactory.generateSecret(keySpec).getEncoded
}
def hashPassword(password: String, salt: Array[Byte]): String = {
val salt = new Array[Byte](16)
random.nextBytes(salt)
val hash = pbkdf2(password, salt, DefaultIterations)
val salt64 = Base64.getEncoder.encodeToString(salt)
val hash64 = Base64.getEncoder.encodeToString(hash)
s"$DefaultIterations:$hash64:$salt64"
}
def checkPassword(password: String, passwordHash: String): Boolean = {
passwordHash.split(":") match {
case Array(it, hash64, salt64) if it.forall(_.isDigit) =>
val hash = Base64.getDecoder.decode(hash64)
val salt = Base64.getDecoder.decode(salt64)
val calculatedHash = pbkdf2(password, salt, it.toInt)
calculatedHash.sameElements(hash)
case other => sys.error("Bad password hash")
}
}
My actual code is a little bit more complex, I include a versioned magic word as the first component (ph1:
), which means if I decided to change hashing algorithms or other input parameters that are not encoded in the output value, I can do that by encoding those hashes by updating the magic word to ph2:
, and then I can have code that validates both the old ph1
and new ph2
hashes.