Migrate almost any password-encryption scheme to AuthLogic
Migrating users from a legacy password scheme to AuthLogic without resetting credentials.
- Published
- Reading time
- 2 min read
In the winter of 2008, I wrote the alpha version of PaperC with an old version of acts_as_authentic. In the spring of 2009, we discarded the untested alpha code and decided to rewrite PaperC from scratch. By then, AuthLogic had replaced acts_as_authentic.
The alpha had collected more than one thousand users, and all of them needed access to the beta. We therefore had to migrate their hashed passwords without forcing account resets.
Configure the transition
AuthLogic was designed to replace existing authentication plugins and supports transitions from many password-encryption schemes. It can migrate some versions of acts_as_authentic automatically; this article shows how to support another salt-and-password combination.
A look inside
AuthLogic is modular enough to accept a custom CryptoProvider for the old encryption method. The example below uses AuthLogic 1.4.0 and depends on how that version passes passwords and salts into a provider.
AuthLogic first checks the user’s password with the current encryption method. If it does not match and a transition provider is configured, AuthLogic checks the password with the old method.
To do so it calls matches? on a CryptoProvider class with two arguments: the stored password and an array of encryption arguments (salt and password). The array looks like [raw_password, salt].
Assume that the old application stores passwords this way:
def encrypt_password(password)
Digest::SHA512.hexdigest("--#{salt}--#{password}--")
endThe transition requires a small class that implements the matches? class method expected of an AuthLogic CryptoProvider:
class OldApplicationSha512
# AuthLogic Credentials provides the tokens in the following order:
# [raw_password, salt]
# The old application used '--#{salt}--#{password}--'
def self.matches?(crypted, *tokens)
Digest::SHA512.hexdigest("--#{tokens.last}--#{tokens.first}--") == crypted
end
endHere’s how to wire it up in your user model:
acts_as_authentic :login_field => :email,
:transition_from_crypto_provider => OldApplicationSha512Complete the migration with tests
This provider completes the wiring, but production migration code should also include tests for correct passwords, incorrect passwords, representative legacy salts, and the transition to the new hash.