Skip to main content
Blog

Migration scenario to a safer password store

By 4 september 2015januari 30th, 2017No Comments

Previously, we posted a blogpost about hashed password storage. In our most recent First8 friday videoblog, I presented this strategy. If we have hashed passwords based on a fast algorithm, this post presents a strategy to migrate to slow hashing, as graceful as possible. Until the last possible moment we defer switching over completely. A strategy that can be applied to other changes equally well. In the text below I will use md5 as example for fast hashing and bcrypt for slow hashing. There are obiously alternatives for fast hashing, based on SHA.

A strategy to go from fast-hashed passwords to bcrypt

This time, we have a table that contains passwords hashed with md5, without salt. We wish to migrate, keep user hassle to a minimum and be backwards compatible as long as needed. So here is our plan:

0. Make sure we meet requirements to be able to change the password hashing strategy,

1. Add the column to the database for bcrypted passwords and if users update a password, update the bcrypt column and perform the pre-existing update.

2. Force a few users to change their password to fill the bcrypt column.

3. Add authentication based on bcrypt if that field is not empty, fallback to pre-existing otherwise.

4. Force all uses to update their password within a certain timeframe (say a few weeks or months). Alternatively, fill the bcrypt column (if empty) at every login.

5. Validate that a certain large percentage of users is migrated: decide when to stop waiting.

6. Disable the fallback when bcrypt-field is empty. You are now live with bcrypt.

7. After succesful testing, remove the md5 hashes and all code that interacts with it.

 

Step 0: requirements

1. Users can change their password when they want it.

2. Users can be forced to change their password (with an expiry date or flag)

3. There is a procedure to follow if a user has forgotten her password.

 

Step 1: First functionality for bcrypt

Add a column in the datastore for the slowly hashed passwords.

Add functionality such that the bcrypt column is updated as well as the md5 column. Now you can verify in production that this procedure works.

This only affects users who wish to update their passwords. If this doesn’t work they may or may not complain to support, but it won’t affect their access to the functionality. If needed, we can roll this back and keep the old situation.

Step 2:  Force (or request) a small portion of users to change their password

Now you are sure that a portion of (regular) users will have a bcrypted password. This creates a test panel for the next step, while still being backwards compatible.

Step 3: Authenticate using bcrypt if available

Create functionality to authenticate users using bcrypt. If that fails or if the bcrypt column is empty, then fall back to using the md5 hash. Users won’t notice and you can verify whether the new functionality works. This provides the opportunity to maybe tweak parameters to bcrypt (number of rounds) and monitor performance.

Step 4: Migrate users to bcrypt

This sounds more tricky than it is. The same strategy as in step two can be applied: force password expiration on relative short notice. Be a bit kind to your (regular) users: if they have updated their password recently but just before bcrypt, it is a bit harsh to force them. So, there is an alternative:

Alternatively, change the login validation. At that point you have the plaintext password in your hands, you could use that to update the bcrypt column if it is empty. This, however, introduces a temporary change to the login-procedure that you’ll have to remove on relative short notice.

Step 5: Wait and monitor…

Now users will gradually fill the bcrypt column. Depending on your user-base and business requirements, you may want to wait longer. In the next step we’ll remove the authentication based on md5. For users that still do not have a bcrypted password, you need an alternative path: force them to a password-restore procedure for instance. If you automatically clean users who don’t log in for a while, you may be able to wait for the bcrypt column to be completely full for all active users.

Step 6: Remove md5 authentication

In this step we remove authentication fallback to md5, while keeping the data still in place. Also we keep updating both columns during this period until the final decision falls. You are now effectively fully migrated to bcrypted passwords: yet, if there are unexpected performance issues, you could still roll back to md5 only.

Step 7: Remove md5 hashes

The final decision is made and we reach the point of no return. Remove the md5 column from the database and all references to it from the code. As soon as you release this version you cannot go back: you’ll lose all md5 hashes (except some database backups maybe).