6
votes

This is from the github page:

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

It appears that to access the password method, you need to call it as an attribute from a create method:

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

Okay...fine? But where's the salt now stored? I just don't get it at all, how is this even remotely secure anymore?

To retrieve a hashed password, you need this method:

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

And call it as an attribute:

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

So it appears everything's working without a salt...how does it do this?

This means I only need one column in my database to do with passowords now, right?password or password_hash instead of password_salt | password_hash?

Well then why does the github page say this:

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

And then this is what really gets me:

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

Why are they explaining all of this if bcrypt is handling everything automatically?

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.

Could someone explain how bcrypt stores and generates these salts? Why does it say it handles it all for me and then goes on to tell me how to generate a salt? Do I need to run something like this in my model: self.password_hash = hash(salt + p)

Argh so confused I used to get salts and hashes utterly and now they've changed it all. Terrible, unclear docs...they appear to show you how to use bcrypt without a salt with loads of examples, and then briefly mention how to do it properly with a salt down at the bottom.

Could someone please give me an example how to use the new version of bcrypt to generate a salt and hash, and how to authenticate?

2
Regardless of whether you figure this out, you should probably stop what you're doing and use has_secure_password, which manages for you the extremely non-trivial task of storing passwords securely.meagar♦
Godammit I was fine salting and hashing myself with bcrypt..why do they have to change it all?Starkers
I thought has_secure_password used bcrypt, but I could be wrongmeagar♦
Yeah I think it does too, I just meant manually using bcrypt's methods myself. Anyway, I've come around to has_secure_password now so s'all good.Starkers

2 Answers

13
votes

Okay, the has_secure_password is really cool. You don't need to worry about salts and hashes anymore, the salt and hash are stored as one attribute ( password_digest) in the database.

It's saved in such a way that bcrypt knows which part of the password_digest string is the salt, and what is the hash.

If you're setting up authentication from scratch, you literally need to do the following:

1) Add the bcrypt rails gem:

gem bcrypt-rails

2) Add the has_secure_password method to the model tasked with handling your user records:

class User < ActiveRecord::Base
    has_secure_password
end

3) Make sure your users table has a password_digest column:

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

4) Create a new method to create a a new empty user instance for the form to use:

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

5) In the new view, make a form that creates populates the params hash' :password and :username entries:

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

6) Back in our controller, permit the username and the password using strong params. The whole reason behind strong params is to prevent some cheeky chappy from using dev tools to create their own html form field (such as one pertaining to id) and populating the database with malicious data:

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

    private

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

7) Let's create the create method that will use these permitted entries to create a new user, populated by the form:

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

Set up your routes as you see fit, and that's it! The user record's password_digest column will be automatically populated with one string comprised of a salt appended with the hash of the password. Very elegant.

All you need to remember: password -> password_digest.

In order to authorise the user and signout the user, create a sessions controller with a create method and a destroy method:

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

Hope this helps someone!

8
votes

bcrypt got everything covered for you. Your password digest consists of a few types of information, bcrypt algorithm type, cost, salt and the checksum.

for example:

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

The first part: $2a$ is the variant of the algorithm see: Where 2x prefix are used in BCrypt?

The second part 10 is the cost parameter, you can increase it to slow down the process (logarithmic value) by providing a hash {cost: 12} as the second argument to create.

Now if you call my_password.salt you get "$2a$10$.kyRS8M3OICtvjBpdDd1se" which identifies the part that is being used as the key to creating your checksum.

And finally, your checksum is "UtlvPKO5CmYz1VM49JL7cJWZDaoYWT.". That's the reason if you call create the second time the string is going to be different as another salt will be used.

But as I mentioned earlier you don't need to do anything extra as all these are being taken care of for you.