Bcrypt为同一输入生成不同的哈希值?

时间:2011-12-11 21:55:54

标签: grails bcrypt

我刚刚为我的新grails项目添加了注册功能。为了测试它,我通过发送电子邮件和密码进行了注册。我使用bcrypt算法对密码进行哈希处理,然后将其保存到数据库中。

但是,当我尝试使用注册时提供的相同电子邮件和密码登录时,登录失败。我调试了应用程序,发现当我尝试与数据库中已经散列的哈希值进行比较时,为同一密码生成的哈希是不同的,因此登录失败( Registration.findByEmailAndPassword(params.email,hashPassd) )在LoginController.groovy中返回null )。

这是我的域类Registration.groovy:

class Registration {

   transient springSecurityService

   String fullName
   String password
   String email

   static constraints = {
      fullName(blank:false)
      password(blank:false, password:true)
      email(blank:false, email:true, unique:true)
   }

   def beforeInsert = {
      encodePassword()
   }

   protected void encodePassword() {
      password = springSecurityService.encodePassword(password)
   }
}

这是我的LoginController.groovy:

class LoginController {

   /**
    * Dependency injection for the springSecurityService.
    */
   def springSecurityService

   def index = {
      if (springSecurityService.isLoggedIn()) {
         render(view: "../homepage")
      }
      else {
         render(view: "../index")
      }
   }

   /**
    * Show the login page.
    */
   def handleLogin = {

      if (springSecurityService.isLoggedIn()) {
         render(view: "../homepage")
         return
      }

      def hashPassd = springSecurityService.encodePassword(params.password)
      // Find the username
      def user = Registration.findByEmailAndPassword(params.email,hashPassd)
      if (!user) {
         flash.message = "User not found for email: ${params.email}"
         render(view: "../index")
         return
      } else {
         session.user = user
         render(view: "../homepage")
      }
   }
}

这是我的Config.groovy的一个片段,告诉grails使用bcrypt算法来哈希密码和键控轮数:

grails.plugins.springsecurity.password.algorithm = 'bcrypt'
grails.plugins.springsecurity.password.bcrypt.logrounds = 16

2 个答案:

答案 0 :(得分:34)

Jan是正确的 - bcrypt by design不会为每个输入字符串生成相同的哈希值。但有一种方法可以检查散列密码是否有效,并将其合并到相关的密码编码器中。因此,为控制器中的passwordEncoder bean添加依赖注入(def passwordEncoder)并将查找更改为

def handleLogin = {

   if (springSecurityService.isLoggedIn()) {
      render(view: "../homepage")
      return
   }

   def user = Registration.findByEmail(params.email)
   if (user && !passwordEncoder.isPasswordValid(user.password, params.password, null)) {
      user = null
   }

   if (!user) {
      flash.message = "User not found for email: ${params.email}"
      render(view: "../index")
      return
   }

   session.user = user
   render(view: "../homepage")
}

请注意,您不会对isPasswordValid来电的密码进行编码 - 通过明文提交的密码。

另外 - 完全不相关 - 将用户存储在会话中是个坏主意。 auth主体随时可用并存储用户ID,以便根据需要轻松重新加载用户(例如User.get(springSecurityService.principal.id)。当您是服务器的唯一用户时,存储断开连接的潜在大型Hibernate对象在开发模式下运行良好,但可能是对内存的重大浪费,并迫使您解决被断开的对象(例如,必须使用merge等)。

答案 1 :(得分:19)

BCrypt哈希包含salt,因此该算法为同一输入返回不同的哈希值。请允许我在Ruby中演示它。

> require 'bcrypt'
> p = BCrypt::Password.create "foobar"
=> "$2a$10$DopJPvHidYqWVKq.Sdcy5eTF82MvG1btPO.81NUtb/4XjiZa7ctQS"
> r = BCrypt::Password.create "foobar"
=> "$2a$10$FTHN0Dechb/IiQuyeEwxaOCSdBss1KcC5fBKDKsj85adOYTLOPQf6"
> p == "foobar"
=> true
> r == "foobar"
=> true

因此,BCrypt不能用于以示例中显示的方式查找用户。应该使用替代的明确字段,例如,用户的姓名或电子邮件地址。