This article assumes that your current password storage mechanism involves some irreversible hashing function. In this case we are forced to migrate each user to the new storage algorithm when they login successfully, because we need the original plain text password to generate a new hash. If you're storing user passwords as plain text, or you're using some reversible encryption scheme, you can migrate all your users' password right away without the need for this article.
Here's a simplified example users table from MySQL:
+--------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| username | varchar(255) | NO | UNI | NULL | |
| password | varchar(255) | NO | | NULL | |
+--------------+------------------+------+-----+---------+----------------+
In the above example table:
- The
id
field is an auto-incremented integer field unique to each user record. - The
username
field is a variable-length text field which must be unique to each user record. - The
password
field is a variable-length text field that will contain the hash of each user's password.
We will need to add another field named pw_storage_algorithm
:
+-----------------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| username | varchar(255) | NO | UNI | NULL | |
| password | varchar(255) | NO | | NULL | |
| pw_storage_algorithm | varchar(255) | YES | | NULL | |
+-----------------------+------------------+------+-----+---------+----------------+
The default value (empty or null) for this new field is assumed to be your current password storage algorithm.
Your current login logic might look something like this (as pseudo-code):
Search for user record in database (by username or email).
If user exists:
If currentPasswordStorageAlgorithm(password) equals user.password:
Login success.
else
Login failure.
Else:
Login failure.
Modify the password checking logic in your application to take into account the new field:
Search for user record in database (by username or email).
If user exists:
If user.pw_storage_algorithm is "new_algorithm":
If newPasswordStorageAlgorithm(password) equals user.password:
Login success.
Else:
Login failure.
Else:
If legacyPasswordStorageAlgorithm(password) equals user.password:
Set user.pw_storage_algorithm equal to "new_algorithm".
Set user.password equal to newPasswordStorageAlgorithm(password).
Save the user record.
Login success.
Else:
Login failure.
Else:
Login failure.
Notice the branch where we check the password using the legacy password storage algorithm. If the check is successful we hash the plain text password using the new password storage algorithm, then store this hash in the database. With this setup, whenever a user successfully logs in, the stored password will be swapped for the new algorithm. The next time the user logs in, the new password storage algorithm will be used to check their password.
That's it! Good luck and don't forget to add some tests ;)