4 act as encrypts password
act_as_encrypts_password is a ActiveRecord::Base mixin that allows automatic encryption of passwords.
4.1 Motivation
When implementing a user model then a mandatory requirement is to encrypt the user’s password. This functionality is orthogonal to all other parts of the application: There is no interdependency between the encryption functionality of your user model and any other part of your system.
ActiveRBAC provides the mixin act_as_encrypts_password for ActiveRecord::Base. This mixin implements automatic and transparent password encryption: Whenever you set the password and store the record, the generated methods salt the password and encrypt the salted password with a cryptographic hash function. The salt is randomly generated to provide protection against Rainbow Table attacks.
4.2 First Glance
Let’s have a really quick glance over how to use act_as_encrypts_password. Create a new database table users and a User model and drop in the mixin:
class User < ActiveRecord::Base
acts_as_encrypts_password
end
And you have password encryption on the fly.
4.3 Requirements
The encrypted password mixin can only be used with ActiveRecord::Base classes. The database table of the ActiveRecord must have the following columns:
- password
- password_salt
- password_hash_type
Note that in a later release you will be able to replace “password” by any other string like “passwd” by passing a parameter to act_as_encrypts_password.
- password_hash_type will be made read-only by the mixin and be set to sha1 (although md5 is still supported to be backward compatible with old ActiveRBAC models).
- password_salt will be set to a random 10 character long string whenever the password is encrypted.
- password will store hash(password + salt)
4.4 Reference
When you mark an ActiveRecord::Base class with this mixin then nifty meta programming Ruby magic will modify the class in class in the following way:
- the non-persistent (meaning it is not stored in the database) object variable @password_confirmation and accessor methods for it are added to the class
- the method password_new? is added which retunrs true if the password or the confirmation have been changed or no password has yet been set into the not yet persisted ActiveRecord
- the method password_equals(pw) is added to the class which returns true if the encrypted password equals pw
- the object variable @aahep_delegate is added to the class which stores the necessary state and functionality that is used in the mixin’s implementation but should be hidden hidden from your code
Additionally, the following validations will be added to the ActiveRecord:
- the password_confirmaton is assumed to be equal to the password if password_new? returns true
- the value of password_salt is set to a ten character long random string (generated by Base64-encoding a random string)
- the password is encrypted with sha1(password + salt) between validation and saving the record if the validation succeeded
4.5 Example
File db/migrations/001create_users.rb:
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
# a user needs a login, right?
t.column :login :string, :limit => 20,
:null => false
# columns for acts_as_encrypts_password
t.column :password, :string, :limit => 128, # sha-512 ready
:null => false
t.column :password_salt, :string, :limit => 100,
:null => false
t.column :password_hash_type, :string, :limit => 10,
:null => false
end
# ...
end
File app/model/user.rb:
class User < ActiveRecord::Base
acts_as_encrypts_password
validates_presence_of :login
end
Now we could use this User class in the following way:
$> cd rbac_using_project
$> ./script/console
Loading development environment.
>> user = User.new
=> <#User...
>> user.password_new?
=> true
>> user.login = 'me'
=> "me"
>> user.password = 'password'
=> "password"
>> user.password_confirmation = 'password'
=> "password"
>> user.save!
=> true
>> user.password_new?
=> false
>> user.password_salt
=> "rzTK3WRlPK"
>> user.password
=> "b5f8673fb23319c365e5f0968c55bc17db263e55"
>> require 'digest/sha1'
=> true
>> Digest::SHA1.hexdigst('password' + 'rzTK3WRlPK')
=> "b5f8673fb23319c365e5f0968c55bc17db263e55"
>> user.password_equals?('password')
=> true
>> user.password_equals?('passwd')
=> false
4.6 TODO
- Allow custom column names.
- Allow for using more hash functions and plugging in Procs for Hashing.