views:

334

answers:

4

I'd like to implement a Rails User model that has a DB column called password. I want to make it so that when I call...

user_instance.password = 'cleartext'

the method hashes the cleartext before setting it on the instance like so:

Digest::SHA1.hexdigest(cleartext)

I've tried using a callback, but the problem is that is hashes the pw every time the user is saved, even if the pw isn't updated. So it gets hashed and rehashed over and over.

I tried redefining the password= method...

alias password= old_password=
def password=(cleartext)
  old_password=(Digest::SHA1.hexdigest(cleartext))
end

but got an error saying password= does not exist.

+8  A: 

FYI, you may want to check out restful_authentication plugin as it will do this for you. Why roll your own?

The way acts_as_authenticated does it:

  1. Database/model has a column called "encrypted_password"
  2. Create a virtual attribute called password
  3. Password isn't populated on a find(..) so if the password is blank, don't encrypt
  4. If the password is nonblank, that means the user entered one, so go ahead and encrypt and stuff in encrypted_password

Code snip (random copy-paste from my user class, so don't blindly paste this in):

require 'digest/sha1'
class User < ActiveRecord::Base

  # stuff

  # callback
  before_save   :encrypt_password
  attr_accessor :password

  # methods
    def encrypt_password
      return if password.blank?
      salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
      crypted_password = Digest::SHA1.hexdigest("--#{salt}--#{self.password}--")
    end
Matt Rogish
What is a "virtual attribute"?
Ethan
IMO, It's an accessor method for a variable that is not a default attribute of the active record object (Not present in the database table that the active record model object represents). It is something that acts like though when the accessor methods are in place.
Chirantan
Yep in this case attr_accessor :password defines the "virtual attribute"
Matt Rogish
+1  A: 

In response to Ethan's comment, what is a virtual attribute, a virtual attribute is one that is not in the database, like Matt's attr_accessor :password, this way you can accept that input from the user, but no necessarily store it in that form, in this instance we want to be able to accept a clear text password, but we want to store it encrypted. To do this we have a virtual attribute :password, and in the database we store it as encrypted_password.

railsninja
+1  A: 

Well, you could override the setter:

def password=(value)
  self[:password] = Digest::SHA1.hexdigest(value)
end

And this would always encrypt the value. You don't need a before_save or an attr_accessor.

Ryan Bigg
+1  A: 

Note that it might be a good idea to choose a random n-bit value per user (upon creation of that user) and prepend that to the password when you hash it.

The reason being: if anyone gets a hold of your database, not only can't they immediately see the users' password, they also can't see if two users have identical passwords (important if one of those users grabs your database) and a certain class of hash-cracking attacks (rainbow tables) becomes harder.

Jonas Kölker