Correct practice for using Bcrypt - ruby-on-rails

This guide shows how to use Bcrypt with Rails, but it differs significantly in implementation from this Rails documentation on Bcrypt.
Guide version
Set password
def password=(password)
self.password_digest = BCrypt::Password.create(password)
end
Authenticate password
def is_password?(password)
BCrypt::Password.new(self.password_digest) == password
end
But the documentation does the same thing using built in methods.
Documentation version
Set password
Setting user.password or setting both user.password and user.password_confirmation values, then calling user.save will invoke some callbacks from Bcrypt which will take the password values and generate the digest and save that in the database.
user.password = 'mUc3m00RsqyRe'
user.password_confirmation = 'mUc3m00RsqyRe'
user.save
Authenticate password
The method user.authenticate(password) will return false or the instance variable of user depending on whether or not the password argument matches user.password_digest.
user.authenticate('notright')
user.authenticate('mUc3m00RsqyRe')
Questions
I had always used the documentation version, since I saw it first, but does the guide follow some better practice?
Why does the guide rewrite the wheel? That seems very un-Railsy.
Is this just a difference in versions of Bcrypt or Rails?

The correct way is to use has_secure_password (the documented method) which was available since Rails 3. Maybe the guide was based on a practice prior to Rails 3?

Related

How to update password attribute with has_secure_password?

I used has_secure_password for the User model. Now I am trying to use AJAX to update some of the user's attributes, including password. However, it looks like with has_secure_password, the password attribute no longer exists, replaced by password_digest. So when I am trying to do
user[:password] = "The password passed by AJAX"
user.save!
I got:
ActiveModel::MissingAttributeError (can't write unknown attribute
password)
The question is: What is the right way to update a user's password in this situation? Do I need to manually compute the hash and update the password_digest?
EDIT:
I am using Rails 4.2.1
Normally you just use:
user = User.find 1
user.password = 'Test123456789'
user.save
But, it sounds like you have not added a password_digest column to the users table or have not run the migrations.

Can I use devise encrypted_password with custom authentication?

I have a rails 4 app with devise authentication. I am rebuilding from scratch and would like to write my own authentication system but I have users stored in the database, whose password is stored as encrypted_password, which is what devise uses to store the hashed password. I understand that using bcrypt I should have a password_digestcolumn.
My question is twofold: will bcrypt be able to read what I have stored in my devise encrypted_password column and if so, can I simply rename that database column to password_digestor will this cause problems?
From what I've read, yes, you should be able to just rename the column and use it with your custom authentication.
Refs:
https://github.com/plataformatec/devise/blob/master/lib/devise/models/database_authenticatable.rb#L149-L151
module Devise
def self.bcrypt(klass, password)
ActiveSupport::Deprecation.warn "Devise.bcrypt is deprecated; use Devise::Encryptor.digest instead"
Devise::Encryptor.digest(klass, password)
end
module Models
module DatabaseAuthenticatable
# Digests the password using bcrypt. Custom encryption should override
# this method to apply their own algorithm.
#
# See https://github.com/plataformatec/devise-encryptable for examples
# of other encryption engines.
def password_digest(password)
Devise::Encryptor.digest(self.class, password)
end
and:
https://github.com/plataformatec/devise/blob/master/lib/devise/encryptor.rb#L5-L10
module Devise
module Encryptor
def self.digest(klass, password)
if klass.pepper.present?
password = "#{password}#{klass.pepper}"
end
::BCrypt::Password.create(password, cost: klass.stretches).to_s
end

password_hash/password_salt vs password_digest

I'm currently going through Michael Hartl's book (Ruby on Rails tutorial 3rd edition) and he uses password_digest with his authentication model, I checked out Pragmatic Studio's Rails course and in their sample video, they use passowrd_hash/password_salt in their authentication model. From wikipediaing, it seems that salt is a standard use in cryptography and has a variety of uses for security. All I could really find on password_digest is it works with a rails method has_secure_password.
Is one preferred over the other or do they serve different purposes ultimately?
when using the has_secure_password a lot of the encryption magic is done automatically for you, and a few functions are exposed that make it easier for you to verify the password of a User.
As taken from the has_secure_password documentation:
# Schema: User(name:string, password_digest:string)
class User < ActiveRecord::Base
has_secure_password
end
user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
user.save
# => false, password required
user.password = 'mUc3m00RsqyRe'
user.save
# => false, confirmation doesn't match
user.password_confirmation = 'mUc3m00RsqyRe'
user.save
# => true
user.authenticate('notright')
# => false
user.authenticate('mUc3m00RsqyRe')
# => user
User.find_by(name: 'david').try(:authenticate, 'notright')
# => false
User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe')
# => user
Both systems you mentioned will work, it just comes down on what you wish to use, what options you have available etc.
Manually building a salted hash (based on e.g. SHA1) was indeed a long time the default way to store passwords. However, since a couple of years, it became clear that there need to be better (i.e. more secure) ways to store a password which ensure that even if an attacker could gain access to the raw hashes, they could not easily brute-force them.
As such, in newer Rails versions, passwords are by default saved with has_secure_password using the Bcrypt algorithm which is much more suitable for password storage than a simple salted hash. Note that Bcrypt also uses a salt internally. You just don't have to deal with it yourself.
The method Hartl uses, has_secure_password, uses an algorithm called Bcrypt to salt and hash a password, producing a password_digest string that has three components:
A flag that indicates that the string is a Bcrypt hash
The (unhashed) salt
The result of hashing password+salt
I'm not familiar with the Pragmatic Studio course, but it sounds like they're storing the salt and hash in different attributes.

How to match a new string with the password already encrypted

I am working on a rails 3.2.13 project. I am using devise plugin (devise gem 3.2.2, 1.4.2) for authentication. Using this plugin, how can I validate the current_password field while changing the old password to a new one? Or else, please suggest how I can achieve this by encrypting the given string and matching it with the password already saved without using the devise plugin.
E.g.: One user has encrypted_password like below:
"$2a$10$VrawKYj6zp10XUxbixVzE.7d4QgYjQn9aiuzAuP7fp3PZOLMP5wbu"
while changing the password, if I enter a current_password, it should match the string above (encrypted_password == current_password). How can I validate this?
I believe you need to break your problem down into the following steps:
Determine if the old_password is actually the user's current password.
To do this, you can call:
User.find_by_id([SOME_ID]).valid_password?(old_password)
If this returns true, then you can move on to the next step to begin changing of the password. If it doesn't, then the old_password is incorrect, and you should not allow the changing of password.
The implementation of valid_password? can be found in the Devise gem's /lib/devise/models/database_authenticatable.rb file (at around Line 40). You could use this implementation to roll your own code for validating a password. But, Devise pretty much does it for you if you call valid_password?, so rolling your own seems unnecessary.
If old_password is valid, then verify that new_password matches confirm_new_password.
if (new_password == confirm_new_password)
.
.
.
end
If these match, then set the new password by doing the following:
u = User.find_by_id([SOME ID])
u.password = new_password
u.password_confirmation = confirm_new_password
u.save
You can verify that the password has been changed by:
u.valid_password?(new_password)
Update user with current_password validation:
#user.update_with_password(account_update_params)
# account_update_params - should have :current_password, :password, :password_confirmation
It is default behaviour in Devise::RegistrationsController. If you want update user without password, you should overwrite controller's action
class UsersController < Devise::RegistrationsController
def update_resource(resource, params)
# resource.update_with_password(params)
resource.update_attributes(params)
end
end
Do I understand you right what you want allow users login with encrypted and unencrypted (usual) password?
We have:
user.valid_password?('Password2').should
code on github
So we can overwrite it inside models/user.rb
def valid_password?(password)
encrypted_password == password || super(password)
end

migrating from legacy password to rails devise

I'm migrating from a legacy system that uses simple MD5 unsalted passwords into Devise. While I could roll my own encryptor as recommended on the Devise wiki I actually want to migrate to the bcrypt password mechanism.
This also seems more reasonable than downloading rainbow tables and trying to discover all the plaintext passwords...
So, I'm wondering if there might be any side-effects to the following code, especially around the save! triggering any callbacks that have unintended behavior:
## config/initializers/legacy.rb
require 'bcrypt'
require 'digest/md5'
module Devise
module Models
module DatabaseAuthenticatable
def valid_password?(password)
if self.legacy_password_hash
if ::Digest::MD5.hexdigest(password) == self.legacy_password_hash
## authenticated; now convert to bcrypt password
self.password = password
self.legacy_password_hash = nil
self.save!
return true
else
## so that we don't get a bcrypt invalid hash exception
return false
end
else
return ::BCrypt::Password.new(self.encrypted_password) == "#{password}#{self.class.pepper}"
end
end
end
end
end
Shamelessly stolen from:
http://groups.google.com/group/plataformatec-devise/browse_thread/thread/9dcf87b2225bd11f?pli=1
In short, do not override Devise's default authentication. Just put this method into your authentication model (usually User).

Resources