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.