Ruby on Rails Password Hashing Module
This is a very simple password module that is also easy to use. Simpy place it in /lib inside your Ruby on Rails application and start protecting your passwords today. This code uses a long hash, and creates individual salts for each password stored. It should be very computationally expensive for someone to crack every password in your database, were they to fall into the wrong hands. Of course, if your database is in the wrong hands, you probably have bigger problems. But even some large sites have been caught storing passwords in plain text.
Ruby Password Hashing Code
require ‘digest/sha2‘ # This module contains functions for hashing and storing passwords module Password # Generates a new salt and rehashes the password def Password.update(password) salt = self.salt hash = self.hash(password,salt) self.store(hash, salt) end # Checks the password against the stored password def Password.check(password, store) hash = self.get_hash(store) salt = self.get_salt(store) if self.hash(password,salt) == hash true else false end end protected # Generates a psuedo-random 64 character string def Password.salt salt = .. 64.times { salt << (i = Kernel.rand(62); i += ((i < 10) ? 48 : ((i < 36) ? 55 : 61 ))).chr } salt end # Generates a 128 character hash def Password.hash(password,salt) Digest::SHA512.hexdigest("#{password}:#{salt}") end # Mixes the hash and salt together for storage def Password.store(hash, salt) hash + salt end # Gets the hash from a stored password def Password.get_hash(store) store[0..127] end # Gets the salt from a stored password def Password.get_salt(store) store[128..192] end end
HTML code generated by vim-color-improved v.0.3.2.Download this code: password.rb
Usage
Using the the password module is simple. All you need to do is save the file above as “password.rb” in the lib directory of your rails project. Then require_dependency “password” in your application.rb. Once that is done you are free to use the functions in any controller.
Example
application.rb
# Filters added to this controller will be run for all controllers in the application. # Likewise, all the methods added will be available for all controllers. require_dependency ‘password‘ class ApplicationController < ActionController::Base end
HTML code generated by vim-color-improved v.0.3.2.
account_controller.rb
This is an example account controller.
class AccountController < ApplicationController layout ‘standard‘ before_filter :login_required, :except => [:login] def login case request.method when :post if session[:user] = User.authenticate(params[:user_login], params[:user_password]) flash[:notice] = ‘Login successful‘ else session[:user] = nil flash.now[:notice] = ‘Login unsuccessful‘ @login = params[:user_login] end end end def logout session[:user] = nil end def welcome end end
HTML code generated by vim-color-improved v.0.3.2.
user.rb
This is the model for the user class. As you can see, password checking against a hashed password is very simple here. Authenicating the user returns a User object, which is stored in the session[:user] variable in the controller above.
class User < ActiveRecord::Base # Checks login information def self.authenticate(nick, pass) user = find(:first, :conditions => ['nick = ?',nick]) if Password::check(pass,user.password) user else return false end end protected # Hash the password before saving the record def before_create self.password = Password::update(self.password) end end
HTML code generated by vim-color-improved v.0.3.2.
October 15th, 2007 at 6:46 am
Glad to see that there’s some more modules hanging around where people are taking password hashing seriously. It’s nice to see someone actually think:
* Salts … bloody good idea
* Random salts … even better
* Hashing using something a bit stronger than MD5 (not saying that MD5 isn’t good … just SHA512 is better)
However there are a few points with your algorithm that you may want to consider in future versions (I apologise if there is anything that Rails does automagically that I’m mentioning here)
* You’re only hashing the password once. Now even though SHA512 is a good hashing algorithm you may want to consider passing it through the algorithm a few more times. I think the RSA recommended amount is 1,000 times! Seems a bit excessive but as you’ve mentioned that this procedure is a very small amount of the entire lifetime of a user on a website so it’s not that bad to CPU load this section
* There is no normalisation of user input. Imagine a situation where a user enters a password which contains a non-ASCII character on a Windows 2000 box & then attempts to re-login on a Linux box. There’s a risk that the encodings both OSs use could cause problems with byte representations & digesting. It would be worthwhile to ensure that you’re in UTF8 or some kind of universal character set
* Some databases can have problems storing non-ASCII Strings (Oracle springs to mind). So before storage I’d encourage using BASE64 encoding of the byte digest and storing this instead.
However the module is very good in its current state & something which really is a step in the right direction
October 23rd, 2007 at 2:03 am
Hi.
Love your password lib - it saved me for some headache.
At the moment we are 2 developers working on a project, and i have a database with users and hashed passwords.
Is it true that a hash string, generated on my server won’t work on his server? We entered the exact same password hash into his user db, but it wouldn’t authenticate - if we generated a new hash on his server everything worked fine.
Best Regards
Mikkel Riber
January 23rd, 2008 at 1:24 pm
[...] Ruby on Rails Password Hashing Module Shared with shareomatic.com [...]
February 26th, 2008 at 8:33 am
Where can I download this library - is it an available Gem on RubyForge?
February 26th, 2008 at 5:12 pm
I’ve updated this page so that you can download the password.rb file. Just look at the bottom of the source code for a link to download it.
February 27th, 2008 at 10:37 pm
Got it! Thanks a ton - now to put it to work.
July 11th, 2008 at 5:29 am
A few points: Your code gave me a little headache, since it uses dots (.) where ruby expects quotes (’ or “), but this was solved very fast.
Second, in User.rb, the password will only be hashed, when the user is created. If you change the password, it just stores the plain password.
So instead of using before_create you could just overwrite the assignment method:
def password=(pass)
write_attribute(:password, password = Password::update(pass))
end
Now the password will always be hashed.
Best Regards
Thomas
P.S.: be sure to remove before_create, otherwise your password will be hashed twice and you cannot login.
July 11th, 2008 at 7:51 am
I have to comment myself:
the assignment-method works, but the cost is, you cannot validate the password (e.g. against a minimum length), because validations uses the hashed value of the password. A better approach is:
protected
def before_save
self.password = Password::update(self.password) if self.password_changed?
end
But even here, you have to validate like this:
def validate
if self.password_changed?
errors.add(”password”, “at least 10 characters”) if self.password.length < 10
end
end
since all other validation-methods will use the hashed-version of the password, when you e.g. change some other attribute but not the password.
Best regards
Thomas
July 11th, 2008 at 8:29 am
@Thomas
Good points. These are very simplified examples of code that is in a CMS I wrote. I actually use a before_save method to check the values of password and password_verification fields (which are submitted from a form), along with additional validations to ensure that the password is valid (length, complexity, etc…)
For multiple reasons, I haven’t provided complete examples, but enough that you can work with the password.rb lib.
I’m glad you found it useful, and I’ll look into the source files to see why the quotes aren’t working properly.
July 29th, 2008 at 6:59 pm
Is this code released under a specific license? I would like to use it in my project under the AGPL.
September 5th, 2008 at 10:58 pm
@Thomas
Instead of before_create, you could use after_validate_on_create.