Upgrade password hash from md5 to bcrypt

merlinbeard picture merlinbeard · Sep 30, 2014 · Viewed 9.2k times · Source

Its been discussed here before, but there seems to be no conclusion.

Ideally, don't want to maintain state (upgraded/not upgraded) in the database etc. so, here is what I'm thinking:

bcrypt the MD5'd password, and use "username + something else" as a salt.

  1. Does this scheme make any sense?
  2. Also, in general is it a good idea to use the username as a part of the salt? I read somewhere that adding a different salt to each hash makes it more secure. Is that correct (especially in context of bcrypt)?

Answer

martinstoeckli picture martinstoeckli · Sep 30, 2014

Surely it is a good idea to switch to a more secure hash algorithm. There is a function password_hash() you can use for creating a BCrypt hash:

// Hash a new password for storing in the database.
// The function automatically generates a cryptographically safe salt.
$hashToStoreInDb = password_hash($password, PASSWORD_DEFAULT);

// Check if the hash of the entered login password, matches the stored hash.
// The salt and the cost factor will be extracted from $existingHashFromDb.
$isPasswordCorrect = password_verify($password, $existingHashFromDb);

From your answer i guess that you used an unsalted MD5 value, so double hashing can be a good solution here. Just pass the MD5 hash to the password_hash() function, it will generate a safe salt on its own.

// Migrating the old MD5 hashes to MD5-BCrypt
$hashToStoreInDb = password_hash($existingMd5Hash, PASSWORD_DEFAULT);

For verification first check for a double hash, and then verify the password accordingly.

if (checkIfDoubleHash($existingHashFromDb))
{
  $isPasswordCorrect = password_verify(MD5($password), $existingHashFromDb);

  // Update database with pure BCrypt hash
  if ($isPasswordCorrect)
    $hashToStoreInDb = password_hash($password, PASSWORD_DEFAULT);
}
else
{
  $isPasswordCorrect = password_verify($password, $existingHashFromDb)
}

The stored hashes can be recognized by the leading $ or by a separate db field, a BCrypt hash for example always starts with a $ character, an MD5 hash does not.

A salt should not be derrived from other parameters and it should be unique per password. The password_hash() function will take care of this. Since a rainbowtable must be built fore each salt, an attacker would have to build a rainbowtable for each password. For more information you can have a look at my tutorial about secure password storing.