盐在bcrypt中发生了什么?

6
这是来自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页面会说这个:

But even this has weaknesses -- attackers can just run lists of possible passwords through the same algorithm, store the results in a big database, and then look up the passwords by their hash:

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)
我曾经完全理解盐值和哈希值,现在他们改变了一切,让我感到困惑。文档很糟糕,不清晰......它们似乎向您展示如何使用没有盐值的bcrypt,并且有很多示例,然后在底部简要提到如何正确地使用盐值。
请问有人能给我一个如何使用新版本的bcrypt来生成盐值和哈希值以及如何进行身份验证的示例吗?

无论你是否能够解决这个问题,你都应该停止当前的操作并使用 has_secure_password。它可以为你管理存储密码这一极其复杂的任务,确保密码的安全性。 - user229044
我本来用bcrypt很好地进行盐值哈希,为什么他们要改变它呢? - Starkers
我认为 has_secure_password 使用 bcrypt,但我可能错了。 - user229044
是的,我也这么认为,我只是指手动使用bcrypt的方法。无论如何,我现在已经接受了has_secure_password,所以一切都很好。 - Starkers
2个回答

13
好的,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) 确保您的用户表具有 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视图中,创建一个表单,填充params哈希的:password:username条目:

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

6) 回到我们的控制器,使用strong params允许用户名和密码。强参数背后的整个原因是为了防止一些聪明的人使用开发工具创建自己的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方法的sessions控制器:

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

希望这能帮助到某些人!

8

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,字符串将不同,因为将使用另一个salt。
但正如我之前提到的,您无需额外操作,因为所有这些都已为您处理。

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