bcrypt 的盐怎么了?

Posted

技术标签:

【中文标题】bcrypt 的盐怎么了?【英文标题】:What has happened to the salt with bcrypt? 【发布时间】:2014-01-31 20:48:24 【问题描述】:

这是来自github页面:

require 'bcrypt'

class User < ActiveRecord::Base
  # users.password_hash in the database is a :string
  include BCrypt

  def password
    @password ||= Password.new(password_hash)
  end

  def password=(new_password)
    @password = Password.create(new_password)
    self.password_hash = @password
  end
end

看来要访问密码方法,您需要从 create 方法中将其作为属性调用:

@user.password = user_params[:password]
@user.save

好的……好吗?但是盐现在存放在哪里?我完全不明白,这怎么可能再远程安全了?

要检索散列密码,您需要这个方法:

  def password
    @password ||= Password.new(password_hash)
  end

并将其称为属性:

  if @user.password == params[:password]
    give_token
  else
    false
  end

所以看起来一切都在没有盐的情况下工作......它是如何做到的?

这意味着我现在只需要数据库中的一列来处理密码,对吗?passwordpassword_hash 而不是 password_salt | password_hash

那么为什么github页面会这样说:

但即使这样也有弱点——攻击者可以运行列表 可能的密码通过相同的算法,将结果存储在 大数据库,然后通过哈希查找密码:

PrecomputedPassword.find_by_hash(<unique gibberish>).password #=> "secret1"
Salts

然后这才是真正让我着迷的:

The solution to this is to add a small chunk of random data -- called a salt -- to the password before it's hashed:

如果 bcrypt 自动处理所有事情,他们为什么要解释所有这些?

hash(salt + p) #=> <really unique gibberish>
The salt is then stored along with the hash in the database, and used to check potentially valid passwords:

<really unique gibberish> =? hash(salt + just_entered_password)
bcrypt-ruby automatically handles the storage and generation of these salts for you.

有人能解释一下 bcrypt 如何存储和生成这些盐吗?为什么它说它为我处理了这一切,然后继续告诉我如何生成盐?我需要在我的模型中运行这样的东西吗:self.password_hash = hash(salt + p)

Argh 太困惑了,我曾经完全得到盐和哈希,现在他们已经改变了这一切。 糟糕的、不清楚的文档...它们似乎向您展示了如何在没有盐的情况下使用 bcrypt 并提供大量示例,然后简要提及如何正确地使用盐在底部。

谁能给我一个例子,如何使用新版本的 bcrypt 生成盐和哈希,以及如何进行身份验证?

【问题讨论】:

不管你是否明白这一点,你应该停止你正在做的事情并使用has_secure_password,它为你管理非常重要的存储任务密码安全。 Godammit 我很好地用 bcrypt 腌制和散列自己......为什么他们必须改变这一切? 我以为 has_secure_password 使用了 bcrypt,但我可能错了 是的,我也这么认为,我只是说自己手动使用 bcrypt 的方法。不管怎样,我现在已经联系了has_secure_password,一切都很好。 【参考方案1】:

好的,has_secure_password 真的很酷。您不再需要担心盐和哈希,盐和哈希作为一个属性(password_digest)存储在数据库中。

它的保存方式是 bcrypt 知道password_digest 字符串的哪一部分是盐,以及什么是哈希。

如果您是从头开始设置身份验证,则实际上需要执行以下操作:

1) 添加 bcrypt rails gem:

gem bcrypt-rails

2) 将has_secure_password 方法添加到负责处理用户记录的模型中:

class User < ActiveRecord::Base
    has_secure_password
end

3) 确保您的 users 表有一个 password_digest 列:

class CreateUser < ActiveRecord::Migration
    create_table :users do |t|
        t.username
        t.password_digest
    end
end

4) 创建一个new 方法来创建一个新的空用户实例供表单使用:

class UsersController < ApplicationController
    def new
        @user = User.new
    end
end

5) 在new 视图中,创建一个表单来填充参数哈希':password:username 条目:

<% form_for( @user ) do |f| %>
    <%= f.text_field :username %>
    <%= f.password_field :password %>
<% end %>

6) 回到我们的控制器,使用强参数允许用户名和密码。强参数背后的全部原因是为了防止一些厚脸皮的家伙使用开发工具创建自己的 html 表单字段(例如与 id 相关的字段)并用恶意数据填充数据库:

class UsersController < ApplicationController
    def new
        @user = User.new
    end

    private

    def user_params
           params.require(:user).permit(:username, :password)
    end
end

7) 让我们创建 create 方法,该方法将使用这些允许的条目来创建一个新用户,由表单填充:

class UsersController < ApplicationController
    def new
        @user = User.new
    end

    def create
            @user = User.new(user_params)
            @user.save
            redirect_to root_path
    end

    private

    def user_params
           params.require(:user).permit(:username, :password)
    end
end

设置您认为合适的路线,就是这样!用户记录的password_digest 列将自动填充一个字符串,该字符串由附加密码哈希的盐组成。十分优雅。

所有你需要记住的:password -> password_digest

为了授权用户和注销用户,使用 create 方法和 destroy 方法创建一个会话控制器:

def create
  user = User.find_by_email(params[:email])
  if user && user.authenticate(params[:password])
    session[:user_id] = user.id
    redirect_to admin_root_path, :notice => "Welcome back, #user.username"
  else
    flash.now.alert = "Invalid email or password"
    redirect_to root_path
  end
end

def destroy
  reset_session
  flash[:info] = "Signed out successfully!"
  redirect_to root_path
end

希望这对某人有所帮助!

【讨论】:

【参考方案2】:

bcrypt 为您提供了一切。您的密码摘要包含几种类型的信息,bcrypt 算法类型、成本、盐和校验和。

例如:

my_password = BCrypt::Password.create("my password")
#=> "$2a$10$.kyRS8M3OICtvjBpdDd1seUtlvPKO5CmYz1VM49JL7cJWZDaoYWT."

第一部分:$2a$是算法的变种见:Where 2x prefix are used in BCrypt?

第二部分10 是成本参数,您可以通过提供哈希cost: 12 作为create 的第二个参数来增加它以减慢进程(对数值)。

现在,如果您调用my_password.salt,您会得到"$2a$10$.kyRS8M3OICtvjBpdDd1se",它标识了用作创建校验和的密钥的部分。

最后,您的校验和是"UtlvPKO5CmYz1VM49JL7cJWZDaoYWT."。这就是如果您第二次调用create 时字符串将不同的原因,因为将使用另一种盐。

但正如我之前提到的,您不需要做任何额外的事情,因为所有这些都会为您处理好。

【讨论】:

以上是关于bcrypt 的盐怎么了?的主要内容,如果未能解决你的问题,请参考以下文章

bcrypt 哈希究竟如何防止彩虹表查找?

什么是盐轮以及盐如何存储在 Bcrypt 中?

将密码哈希从 md5 升级到 bcrypt [关闭]

生成具有恒定轨道的盐

更改盐没有效果:为啥散列密码不会因不同的盐而改变?

使用Bcrypt对密码进行加密与解密验证