Should passwords be automatically reset when the underlying method changes
Your option 1. is a bad idea: in addition to the User-Experience / Public-Relations reasons you state, you're also giving attackers a window to intercept the password reset tokens and compromise every account on your server. It also doesn't solve your problem if you have even one user who's too lazy to log in / update their password.
At first glance, both 2. and 3. seem fine to me. Your #2 is no less secure than what you're doing now, but 2. would mean you have to continue supporting the current weak login forever (or do something like "After X months we're wiping your password and forcing you to do a recovery" which breaks the nice user-transparency you want, so let's ignore that).
Let's consider the case where you have users in the DB who will never log in again. With both 2. and 3. you have to continue to support the current hashing alg in your code-base forever just in case they do log in, but at least 3. has the advantage that they (or rather, you) are protected against offline brute-force attacks if your DB is ever stolen.
Since you'll have to keep the "old style flag" column around forever, do yourself a favour and make it an int
not a bool
so that if you ever have to update your password hashing alg again, you can record which old style they are on.
UPDATE: A very similar question was asked here and built on the discussion from this thread.
If you can do option 3, I don't see why you would even consider the others. It is by far the best option. With this option, my gut feeling would be to consider using two different salts, one for the old algorithm and one for the new one with bcrypt. I'm envisioning a set up like this:
- Set up your new password system how you would if you were starting today.
- Create a new separate table that has fields for username(or id), hashing algorithm (name or id), salt.
- On login, check if the user has a record in the separate table, and if so, hash the password the old way, then hash the result the new way and compare with the bcrypt hash. If it matches, re-salt/hash the password the new way, and delete the record from the separate table.
The downside is you'll have to keep the password in memory a few milliseconds longer (who cares) and you'll have the extra table lookup on every login, pretty much forever, until either the separate table is empty or until old accounts become stale enough that you are willing to require them to reset their password themselves.
Note that, if your OLD scheme is hashed with salt, you will not be able to use scheme #3, unless you store the salt SEPARATELY.
Normally the salt is stored together with the hash, and you use the salt as input to the hash function - if you do not use exactly the same salt, you will get a completely different output.
If you newhash(oldsalt+oldhash, newsalt), then, even having the correct password, you will not be able to recreate oldhash (since you do not have oldsalt), and you cannot generate the final hash. Same thing applies to anything that has parameters (e.g. bcrypt has a "cost" parameter - this needs to be set when encrypting, and is embedded in the output, for use when validating the password).
ALSO: as was mentioned by others, if you are storing that the hash is "old" or "new" style, consider instead storing the "scheme" - where, e.g. 0 is the "old", and 1 is bcrypt (note I do not use "new" - it is "new" now, will not be "new" forever!). A common way to do this is to have a marker at the start of the hash (this may already be the case!). bcrypt uses one of the following standard prefixes: "$2a$", "$2b$", "$2x", or "$2y$". Depending on the possible outputs of your "old" algorithm, you may need to make up your own prefix to mark these, or you may be able to get away with 'anything that doesn't start with '$' is the old algorithm.
And finally, since you are obviously concerned (rightly so!) with the security of the old passwords, I would suggest forcing everyone to change their password, by emailing them instructions with a token (DO NOT! SEND A LINK! You do not want your users clicking on a link! Just tell them to log on to the usual place). Then, ask for the token AND their password. Otherwise, someone who has stolen a password in the past can change the password, and get a valid "new" password.
FINALLY: have an expiry date - if passwords are not changed by this date, then they should be invalidated. This date should be in the email, and not too far in the future (a week? depends on how long your customers take to respond). After that, they will have to go through "password reset" procedures.