如何在Play框架中使用哈希函数(可能是BCrypt)对密码进行加密?

27

我对 Play 框架和密码哈希有些陌生。我尝试查找一些对密码进行哈希的解决方案,发现了 BCrypt。您认为这足够安全吗?如果是,我该如何在 Play 框架中使用它呢?(我正在使用 Play 2.1.3)谢谢!

4个回答

53

这是我写的一个使用 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 哈希值的代码。


1
如果BCrypt使用盐来创建哈希值,那么您需要向用户模型添加一个盐,不是吗?在您的示例中没有“盐”字段。 - Incerteza
6
不,BCrypt在输出的值中包含明文盐。我不确定其格式是否完全准确,但大概是“随机盐在这里:盐值哈希在这里”。因此,首先需要解析该值,提取出盐和哈希值,然后既有哈希值又有盐可以用于密码检查。 - James Roper

8

6

0

这里是最新的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
      }
    }
  }

请查看更多内容:https://code.linedrop.io/guides/Ultimate-Guide-to-Building-a-Web-Application-with-Play-and-Scala/Utilities#PasswordHashing 哈希实用程序示例:https://github.com/LineDrop/play-scala-web-application/blob/master/app/utilities/Hash.scala 用户模型示例:https://github.com/LineDrop/play-scala-web-application/blob/master/app/models/User.scala

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接