Skip to content

How To: Create a custom encryptor

Elberet edited this page Feb 13, 2012 · 8 revisions

When working with a legacy database where a non-supported password encryption method is used, you can write your own custom encryptor:

# lib/devise/encryptors/md5.rb
require 'digest/md5'

module Devise
  module Encryptors
    class Md5 < Base
      def self.digest(password, stretches, salt, pepper)
        str = [password, salt].flatten.compact.join
        Digest::MD5.hexdigest(str)
      end
    end
  end
end

The above will authenticate using non-salted MD5-hashed passwords (a terrible idea, but such are legacy apps). You can then set this as your encryptor in config/initializers/devise.rb:

config.encryptor = :md5

Don’t forget to enable the :encryptable in your User model.

Also, you should make sure that the new file is loaded, for instance by adding this to your users' class:

require Rails.root.join('lib', 'devise', 'encryptors', 'md5')

See also: http://www.markrichman.com/2010/11/22/rails-devise-datamapper-authentication/

Problems?

Additionally, I had to add the following to my user model in order to be able to log in. Supposedly there is a more elegant way, but I did not get devise_for … :encryptor => :md5 to work, nor devise :encryptor => :md5. Maybe this is because I do not have a password salt.

# app/models/user.rb 
# debt: we should not need to do this, but seems like setting :encryptor => :md5 on the devise or devise_for
# does not do anything. Maybe because we don't use a salt. So this works for now.
def valid_password?(password)
  return false if encrypted_password.blank?
  Devise.secure_compare(Devise::Encryptors::Md5.digest(password, nil, nil, nil), self.encrypted_password)
end

And how I tested it.

# spec/models/user_spec.rb
describe "devise valid_password?" do
  it "should use our hashing mechanism, not the default bcrypt" do
    Factory(:member).valid_password?('blahblah').should be_true
  end
end

Workaround for empty salt

Encryptable refuses to actually encrypt the password when the salt is not present. It can be deceived by returning an empty string from User#password_salt that overrides Rails' present? method, like this:

# app/models/user.rb
EMPTY_SALT = ''
def EMPTY_SALT.present?
  # the default implementation returns true only when the
  # string isn't empty and contains non-whitespace characters.
  true
end

def password_salt
  EMPTY_SALT
end
Clone this wiki locally