I would like to store and validate passwords in a ruby application that does not use devise, and have them be compatible with a future application that does use devise. What is the default password hashing scheme that devise uses, and is it possible to extract and use just this component from devise?
Devise's DatabaseAuthenticatable module uses BCrpyt to hash passwords, wrapped up in the Devise::Encryptor module. The relevant method, digest, is pretty simple:
def self.digest(klass, password)
if klass.pepper.present?
password = "#{password}#{klass.pepper}"
end
::BCrypt::Password.create(password, cost: klass.stretches).to_s
end
klass is only used to fetch a couple parameters: pepper, a string which is appended onto the password pre-hashing but not stored in the database (unlike salt, which is appended as well but stored with the password in the DB); and cost, a measure of how secure the hash should be (see the docs). Both of these are static and you can hard-code them into your non-Devise app (but make sure to keep pepper secret!).
So, your hash method might be written just as:
def self.digest(password)
password = "#{password}#{ENV['PASSWORD_PEPPER']}"
::BCrypt::Password.create(password, cost: 10).to_s
end
Related
I have a Ruby on Rails (4.2.1) Application that use Devise (3.5.1) for authenticate users, we are rebuilding this application in Rails (5.0.0) and we are using Devise (4.2.0). The problem happen when I copy the users table from the old application to the new application, then in the new application I can not login using the old data. In the devise.rb initialiser I'm using the same secret_key in both applications so not sure why I can not login into the new app using the old data, any ideas?
To start with, use rails console to ensure the issue is connected with passwords, and not the app - i.e. that valid_password? call of your user model will fail with proper password.
Devise uses this method by default to generate password hashes:
def self.digest(klass, password)
if klass.pepper.present?
password = "#{password}#{klass.pepper}"
end
::BCrypt::Password.create(password, cost: klass.stretches).to_s
end
It uses klass.pepper to add to your password if present. klass here would be your model (e.g. user), which can be configured to use pepper:
Besides :stretches, you can define :pepper, :encryptor,
:confirm_within, :remember_for, :timeout_in, :unlock_in among other
options.
cost is complexity of salt generation for new password storing, so should only influence new hashes generation, and wouldn't affect validation of previously generated passwords.
Devise uses this method to compare input password with stored hashed value:
def self.compare(klass, hashed_password, password)
return false if hashed_password.blank?
bcrypt = ::BCrypt::Password.new(hashed_password)
if klass.pepper.present?
password = "#{password}#{klass.pepper}"
end
password = ::BCrypt::Engine.hash_secret(password, bcrypt.salt)
Devise.secure_compare(password, hashed_password)
end
Just debug it in your both app versions to see what input parameters in this method might be different, e.g. if you have different pepper defined for your model, bcrypt.salt is different in your two apps for the same hashed_value.
As to where salt is taken from existing stored hashed password, it's really simple. The stored string is simply split by $ symbol:
# call-seq:
# split_hash(raw_hash) -> version, cost, salt, hash
#
# Splits +h+ into version, cost, salt, and hash and returns them in that order.
def split_hash(h)
_, v, c, mash = h.split('$')
return v.to_str, c.to_i, h[0, 29].to_str, mash[-31, 31].to_str
end
My web app is using Spring Security plugin for authentication and authorization. I'm building sort of an API, where I need to verify user password.
Spring Security is configured to use BCrypt with 5 logrounds and username property as salt:
grails.plugins.springsecurity.password.algorithm = 'brcypt'
grails.plugins.springsecurity.password.bcrypt.logrounds = 5
grails.plugins.springsecurity.dao.reflectionSaltSourceProperty = 'username' // password salting
Now, in my controller I'd like to verify user password and login. For this, I call springSecurityService.encodePassword(cmd.password, cmd.username)
where cmd is command object with my params. The thing is, on each request, password encoded with springSecurityService is different and never the same as user password in databse. I tried also with constant values in encodePassword call, something like this:
springSecurityService.encodePassword('foo', 'bar') and result is the same: on each request encoded password is different. This way I can't verify user password and get valid user instance from databse.
Any ideas how to solve this?
bcrypt generates a uniq salt each time, and includes it into result hash. Because of it springSecurityService.encodePasswod just ignores second argument, and reflectionSaltSourceProperty option as well (see sources). So, each time you'll get different hash for same input data.
You can use BCrypt class to validate password, like:
if (BCrypt.checkpw(candidate_password, stored_hash))
System.out.println("It matches");
else
System.out.println("It does not match");
See docs for BCrypt: http://static.springsource.org/autorepo/docs/spring-security/3.1.x/apidocs/org/springframework/security/crypto/bcrypt/BCrypt.html
Btw, as you're using Spring Security, it's already implemented in framework, so you can use passwordEncoder bean:
def passwrodEncoder
...
passwordEncoder.isPasswordValid(user.password, cmd.password, user.username) //user.username will be ignored
I started working on a project which had a lot of code already in place. It is a Ruby on Rail application that uses Devise for user authentication. One of the requirements of the application is that when a user changes their password, they are not allowed to use the same password as the last three passwords they previously used. To acomplish this, there is a table that contains a history of passwords for a given user. These passwords are copies of the encrypted passwords that existed prior to any password change on the user.
Here is where the problem comes in. We have a password change form that collects the new password for a given user. I need to be able to take the new password and encrypt it so that I can match the encrypted value of the new password against encrypted values of the old passwords in history.
Technical Stuff
Rails version 3.0.9
Devise version 1.3.4
Using standard BCrypt with Devise. bcrypt_ruby version 2.1.4
To do this we are overrriding the reset_password method supported by Devise. This allows us to introduce our own method, has_repeated_password in the user controller.
The version of has_repeated_password I started with is below:
def has_repeated_password?
return false if self.new_record? || self.version == 1
histories = self.versions.find(:all, :order => 'version DESC', :limit => 3)
histories.detect do |history|
history.encrypted_password == self.class.encryptor_class.digest(self.password, self.class.stretches, history.password_salt, self.class.pepper)
end
end
The problem here is that the encryptor class is never defined, causing an error every time this routine runs. Even through there are many examples that claim this works, I cannot get it to work when Devise is using the default encryption.
A second attempt at this is the following code:
def has_repeated_password?<br>
return false if self.new_record? || self.version == 1
histories = self.versions.find(:all, :order => 'version DESC', :limit => 3)
histories.detect do |history|
pwd = self.password_digest(self.password)
history.encrypted_password == pwd
end
end
In this case, I never get a password that matches any of the stored passwords, even though I have verified that the password in the database is what I expect.
I have been trying to dig through the Devise code to see what I can find there. I know that the autentication has to do this somehow when it matches passwords collected from users against the stored password.
Any help would be appreciated.
I think I found a solution to my own problem. The key sticking point of this was that I was trying to get an encrypted password that was not part of the user model (any more) tied to Devise. This solution does assume that Devise will be using Bcrypt as the standard encryption tool (can't remember which version of Devise made the move). Bcrypt/Devise actually buries the salt for the password in the encrypted password. If you have the salt and the pepper, you can get the same password to generate the same encrypted value.
So here is the updated code for the routine refernced above:
def has_repeated_password?
return false if self.new_record? || self.version == 1
histories = self.versions.find(:all, :order => 'version DESC', :limit => 3)
histories.detect do |history|
bcrypt = ::BCrypt::Password.new(history.encrypted_password)
password = ::BCrypt::Engine.hash_secret("#{self.password}#{self.class.pepper}", bcrypt.salt)
password == history.encrypted_password
end
end
The key here is that the Bcyrpt object has to be created with an existing encrypted password using the same salt that generated the original password. That is accomplished by giving it my stored historical encrypted password (history.encrypted_password). One of the other key elements is that both the history passwords and the proposed new password use the same pepper, which is managed by Devise. So by using the Engne.has_secret call with the intended new password, it can be compared with the history password.
I had to move the bcrypt code into here because all the password methods supported by Devise assume that you want to act on the user password of the current user object.
In my Rails app, I periodically require the user to re-enter their password after a certain amount of inactivity--like with sudo on Linux. My app uses Authlogic for authentication and handling password storage and encryption.
I need some method to encrypt the password the user enters using the exact same encryption scheme Authlogic uses to encrypt passwords when it verifies passwords during authentication. I need to 1) encrypt the password the user enters and 2) do a string comparison between this encryption and the encrypted password stored in the database for the user.
Where should I put the method to perform this encryption? Here are some ideas:
Idea 1 (in a new, custom module)
module PasswordCryption
include Authlogic::ActsAsAuthentic::Password
encrypt_password(password)
end
end
Idea 2 (in the User model)
class User
acts_as_authentic <---- makes Authlogic password encryption functionality available
encrypt_password(password)
end
end
Authlogic uses the SHA512 encryption by default. The clue is that authlogic repeated the hexdigest function 20 times. This will solve your problem:
digest = [raw_password, password_salt].join('')
crypted_password = 20.times { digest = Digest::SHA512.hexdigest(digest) }
I have a working Rails site that uses devise to manage users. For session management, I am using devise's rememberable strategy, which stores and retrieves encrypted authentication information from a user's cookie.
I'm implementing a multi-photo upload widget that uses flash. Flash does not support sending cookies along with requests. This is a problem with multiple multi-upload flash+javascript libraries, so fixing this shortcoming is probably not feasible.
So my question is: can I successfully authenticate to devise/rememberable without using cookies? And if so, how?
More details
Devise/rememberable depends on the value of remember_token within the cookie. If I could fool Rails into thinking that the value was supplied as a cookie (e.g. request.cookies['remember_token'] = '...'), my problem would be solved. Devise/rememberable would find the correct value there, unpack it, and successfully authenticate. However, the request.cookies hash is apparently read-only. Writing to the hash is silently ignored. Example (debug console from an incoming POST request):
>> request.cookies['remember_token'] = 'a string'
=> "a string"
>> request.cookies['remember_token']
=> nil
>> request.cookies
=> {}
I'm using (or trying to use) the FancyUpload v3 widget.
How about overriding Devise slightly?
Based on Devise 1.2.rc something like this should work:
module Devise
module Strategies
class Rememberable
def remember_cookie
# your code to get the hashed value from the request
end
end
end
end
Alternatively, you could add a new (subclassed) strategy:
module Devise
module Strategies
class RememberableParameter < Rememberable
def remember_cookie
# your code to get the hashed value from the request
end
end
end
end
Warden::Strategies.add(:rememberable_parameter, Devise::Strategies::Rememberable)
Or, look into Token Authenticatable:
Token Authenticatable: signs in a user based on an authentication token (also known as
"single access token"). The token can be given both through query string or
HTTP Basic Authentication
There's more about it here:
https://github.com/plataformatec/devise/blob/master/lib/devise/models/token_authenticatable.rb
Good luck!