我对 Play 框架和密码哈希有些陌生。我尝试查找一些对密码进行哈希的解决方案,发现了 BCrypt。您认为这足够安全吗?如果是,我该如何在 Play 框架中使用它呢?(我正在使用 Play 2.1.3)谢谢!
我对 Play 框架和密码哈希有些陌生。我尝试查找一些对密码进行哈希的解决方案,发现了 BCrypt。您认为这足够安全吗?如果是,我该如何在 Play 框架中使用它呢?(我正在使用 Play 2.1.3)谢谢!
这是我写的一个使用 BCrypt 哈希密码的 Play Java 示例项目,可以查看 newUser() 和 signIn() 动作:
https://github.com/jroper/play-demo-twitbookplus/blob/master/app/controllers/UserController.java
在 Scala 中也可以进行类似的操作。简单来说,在 Build.scala 中的依赖项中添加 jbycrpt:
val appDependencies = Seq(
"org.mindrot" % "jbcrypt" % "0.3m"
)
然后使用以下方式哈希密码:
String passwordHash = BCrypt.hashpw(password, BCrypt.gensalt());
并使用以下方式验证密码:
BCrypt.checkpw(password, passwordHash)
更新(2020年)
在我最近的项目中,我不再使用BCrypt,而是使用PBKDF2哈希,这有一个优点,它不需要任何额外的依赖,但缺点是需要编写更多的代码并手动管理盐。BCrypt还存在一些问题,即不同的实现无法准确地消费彼此的输出,甚至有些实现会截断长密码,这是非常糟糕的。尽管它需要更多的代码,但我喜欢这种方法,因为它让我拥有更多的控制权,并且可以透明地显示事物是如何工作的,以及随着散列算法和输入参数的建议不断改变,使得更新变得容易。
无论如何,下面是我使用的代码,它将盐和使用的迭代次数(以便可以按照最佳实践逐步增加它们)存储在散列值中,用冒号分隔:
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")
}
}
我的实际代码稍微复杂一些,我将一个版本化的“魔术词”作为第一个组件包含在内(ph1:
),这意味着如果我决定更改哈希算法或其他未编码在输出值中的输入参数,则可以通过更新魔术词到 ph2:
来编码这些哈希,然后我可以拥有验证旧的 ph1
和新的 ph2
哈希值的代码。
这里是最新的bcrypt t3hnar库版本4.1与Play框架配合使用的说明:
将依赖项添加到build.sbt文件中:
libraryDependencies += "com.github.t3hnar" %% "scala-bcrypt" % "4.1"
// Reference: https://github.com/t3hnar/scala-bcrypt
package utilities
import com.github.t3hnar.bcrypt._
import play.api.Logging
import scala.util.Success
import scala.util.Failure
object Hash extends Logging {
private val log = Log.get
def create(value: String): String = {
log.debug("Encrypting a value")
// Creating a salted hash
val salt = generateSalt
val hash = value.bcrypt(salt)
// return hashed value
hash
}
def validate(value: String, hash: String): Boolean = {
// Validating the hash
value.isBcryptedSafe(hash) match {
case Success(result) => { // hash is valid - correct salt and number of rounds
log.trace("Hash is safe")
if (result) log.trace("Test hash matches stored hash") else log.trace("Test hash does not match stored hash")
result // true if test hash matches the stored has, false if it does not
}
case Failure(failure) => {
// Hash is invalid
log.trace("Hash is not safe")
false
}
}
}
}
使用示例:
// Password hashing
val hash = Hash.create(password)
// Password validation
def authenticate(email: String, password: String): Option[User] = {
log.debug(s"Authenticating user: $email")
// Get user
read(email) match {
case Some(user) => {
// Compare password with the hashed value
if (Hash.validate(password, user.hash)) Some(user) else None
}
case None => {
// Cannot find user with this email
log.trace(s"User not found")
None
}
}
}