4

I am keen to migrate my code to the new password_* functions provided natively by PHP.

The existing hashes in the database have been generated as follows:

hash ('sha512', '<A constant defined earlier>' . $email . $password);

I'd like to move these to be hashes created by the now-recommended:

password_hash ($password, PASSWORD_DEFAULT);

Obviously, when a user logs in, I can take the opportunity to create the new hash from the password they just provided, and save that in the database.

However, I'd like to avoid having to have two fields in the database, namely one for the deprecated hash and one for the modern password_hash one. Instead, I'd rather just replace the old ones as each user logs in.

Therefore, is it possible to keep a single database field, and have the userland code determine whether the hash is old, i.e. determine which check to use?

(I'm assuming that the hash('sha512') hashes cannot be automatically upgraded to crypt() ones?)

fooquency
  • 1,575
  • 3
  • 16
  • 29

2 Answers2

4

Hashes created with password_hash will have a very distinctive $2y$ string at the beginning (or similar $..$, as long as you're operating with the current default Blowfish cypher), while SHA256 will simply be all hex values. Therefore, you can simply test whether a value is a legacy hash value or a password_hash value:

function isLegacyHash($hash) {
    return !preg_match('/^\$\w{2}\$/', $hash);
}

Using this, you can keep both types of hashes in a single field and upgrade them when the user logs in. Alternatively, you could simply set a flag in a column like hash_version.

deceze
  • 510,633
  • 85
  • 743
  • 889
  • Thanks; this is along the lines of what I was considering, as database migrations come with a degree of pain that simply upgrading a library doesn't involve. However, I see from http://www.php.net/crypt (which password_* wraps) that $..$ is not always used, e.g. with CRYPT_STD_DES or CRYPT_EXT_DES. It's not clear to me whether password_* would ever use those. – fooquency Dec 30 '13 at 14:57
  • `password_` currently only supports one algorithm: Blowfish. If you use `PASSWORD_DEFAULT` this *may* change in the future with newer PHP versions, though I doubt it will use an algorithm which spits out pure hex (though that's not guaranteed). If you want to make sure it doesn't change inadvertently, explicitly use `PASSWORD_BCRYPT`. – deceze Dec 30 '13 at 15:02
  • Use `PASSWORD_DEFAULT` and alter your password column to `VARCHAR(255)`. The reason encryption might change in the future is to ensure the best and safest encryption is always in use. As long as you're checking to see if a rehash is needed and rehashing, you're future proof. – Jeremy Kendall Dec 30 '13 at 16:00
2

You will have to rehash when a user logs in. But there is a function to check already, see password_needs_rehash.

So when a user logs in, run the check and change the password hash if it needs a rehash.

It will be a little more tricky if you decide to completely migrate to bcrypt at some point. Then you will need to think about what to do with the users who have not had a new hash created.

The following code will return true so you know you need to rehash.

$password = 'test';
$oldHash = hash('sha512',); // get old Hash from DB
if (password_needs_rehash($oldHash, PASSWORD_BCRYPT)) {
    $newHash = password_hash($password , PASSWORD_BCRYPT);
    // save new Hash to DB (IMPORTANT: only if log in was successful...)
}
Patrick
  • 922
  • 11
  • 22
  • Thanks; yes, I'm planning to use password_needs_rehash which is needed since PASSWORD_DEFAULT will be set, and the default algorithm could change in future PHP versions. However, password_needs_rehash won't detect a hash('sha512') has presumably, so isn't useful for the initial upgrading process. – fooquency Dec 30 '13 at 15:05
  • I added a code example. You can run the function and it will return true if it is a sha512 hash and you can then use the new hashing function. It won't have to detect the sha512 hash, it only needs to detect if it is not a valid bcrypt hash. – Patrick Dec 30 '13 at 15:13
  • 1
    Ah, that is a useful clarification - thanks. Presumably PASSWORD_DEFAULT would be safe to use too within password_needs_rehash()? I'd rather not lock the library down to PASSWORD_BCRYPT explicitly. – fooquency Dec 30 '13 at 15:14
  • 1
    `password_needs_rehash` will accept a `sha512` hash and return true. As in my comment above, I recommend `PASSWORD_DEFAULT` (and updating your password column to `VARCHAR(255)`) for future proofing. – Jeremy Kendall Dec 30 '13 at 16:01