Mooktakim Ahmed

  • Archive
  • RSS

Upgrading old authentication to new Devise

With the recent issues with Linkedin and other websites being hacked, and all their unsalted hashed passwords being leaked. You are thinking of upgrading your authentication to latest & greatest.

This article covers how to move to a new authentication system or to amend your existing system to a new algorithm.

Recently, I have been working on an old web app that uses restful-authentication plugin. This plugin uses SHA1 hashing. Fortunately, this setup does include salting. So we have two fields in our Users table, crypted_password and salt.

I have decided to upgrade this to Devise, with bcrypt. It should keep everything safe for few more years.

Follow the instructions on installing devise on github. When you come to generating the migration file, rename the old columns to old_crypted_password and old_salt, or to whatever you might prefer.

So how do we move over to a new authentication system without having to get every user to set the password again?

The best way to do this without effecting the user is to set the new hash during login process.

With devise, you get the authentication feature through the database_authenticatable module.

The method we want to override is:

def valid_password?(password)
  ...
end

We want to check to see if the user has old_crypted_password set. If this field is not set, we use the normal devise authentication. If the field is set, we want to validate the password with the old hash and reset the password:

def valid_password?(password)
  if old_crypted_password.present?
    logger.debug "Found SHA1 password"
    if sha1_valid_password?(password)
      self.password = password
      self.old_crypted_password = nil
      self.old_salt = nil
      self.save(:validate => false)
      true
    else
      false
    end
  else
    super
  end
end

def sha1_valid_password?(password)
  logger.debug "SHA1 checking password"
  old_crypted_password == sha1_password_digest(password, old_salt)
end

The sha1_valid_password? method could be different for you, so double check. For restful-authentication plugin you have to do the following:

private

def sha1_password_digest(p, s)
  rest_auth_site_key = 'secret'
  rest_auth_digest_stretches = 10

  digest = rest_auth_site_key
  rest_auth_digest_stretches.times do
    digest = sha1_secure_digest(digest, s, p, rest_auth_site_key)
  end
  logger.debug "SHA1 generated: #{digest}"
  digest
end

def sha1_secure_digest(*args)
  require 'digest/sha1'
  Digest::SHA1.hexdigest(args.flatten.join('--'))
end

As you can see I have hard coded the old site_key and and digest strength. Just to clean things up, we’re going to put this in a module:

class User < ActiveRecord::Base

  include UsersBehaviour::Authentication
  include UsersBehaviour::PasswordMigration

end

module UsersBehaviour
  module Authentication

    extend ActiveSupport::Concern

    included do
      # Include default devise modules. Others available are:
      # :token_authenticatable, :confirmable,
      # :lockable, :timeoutable and :omniauthable
      devise :database_authenticatable, :registerable,
             :recoverable, :rememberable, :trackable, :validatable,
             :confirmable

      # Setup accessible (or protected) attributes for your model
      attr_accessible :email, :password, :password_confirmation, :remember_me
    end

  end
end

module UsersBehaviour
  module PasswordMigration

    extend ActiveSupport::Concern

    def valid_password?(password)
      if old_crypted_password.present?
        logger.debug "Found SHA1 password"
        if sha1_valid_password?(password)
          self.password = password
          self.old_crypted_password = nil
          self.old_salt = nil
          self.save(:validate => false)
          true
        else
          false
        end
      else
        super
      end
    end

    def sha1_valid_password?(password)
      logger.debug "SHA1 checking password"
      old_crypted_password == sha1_password_digest(password, old_salt)
    end

    private

    def sha1_password_digest(p, s)
      rest_auth_site_key = 'secret'
      rest_auth_digest_stretches = 10

      digest = rest_auth_site_key
      rest_auth_digest_stretches.times do
        digest = sha1_secure_digest(digest, s, p, rest_auth_site_key)
      end
      logger.debug "SHA1 generated: #{digest}"
      digest
    end

    def sha1_secure_digest(*args)
      require 'digest/sha1'
      Digest::SHA1.hexdigest(args.flatten.join('--'))
    end

  end
end

Users passwords will be hashed with the new algorithm when they login. So over time, all your users should have moved over to the new algorithm.

  • 11 months ago
  • Permalink
Share

Short URL

TwitterFacebookPinterestGoogle+

About

Avatar Ruby on Rails Web Developer based in London, UK

Twitter

loading tweets…

Top

  • RSS
  • Random
  • Archive
  • Mobile
Effector Theme by Pixel Union