【问题标题】:Bcrypt generates different hashes for the same input?Bcrypt 为相同的输入生成不同的哈希?
【发布时间】:2012-01-18 01:31:25
【问题描述】:

我刚刚为我的新 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 中的一个 sn-p,它告诉 grails 使用 bcrypt 算法对密码和密钥轮数进行哈希处理:

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

【问题讨论】:

    标签: grails bcrypt


    【解决方案1】:

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

    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 等)。

    【讨论】:

    • 谢谢伯特。有效。非常感谢有关在会话中存储用户的建议。我是 grails 的新手,正在使用它来开发一个社交网站。如果您能就最佳实践等或任何有帮助的内容提出建议,我将不胜感激……可能是您博客上帖子的链接(一群唯我论者……我喜欢它)
    【解决方案2】:

    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 不能用于以您的示例中介绍的方式查找用户。应使用替代的明确字段,例如用户名或电子邮件地址。

    【讨论】:

    • 如果我想在本地运行它,gem 的名称是什么?
    • 回答我自己的问题,gem 名称是bcrypt-ruby
    猜你喜欢
    • 2017-11-15
    • 2021-05-09
    • 2015-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-17
    • 2020-05-13
    • 1970-01-01
    相关资源
    最近更新 更多