Using MD5 and Password hash function when storing

2019-09-14 11:19发布

问题:

If I have this PHP script for a login system:

    $user = $_POST['user_name'];
    $pass = md5($_POST['user_pass']);

    require_once("connection_file.php");
    $sql = "SELECT * FROM login_table WHERE user_n = :us AND user_p = :password";
    $stmt = $conn->prepare($sql);
    $stmt->bindValue(':us', $user, PDO::PARAM_STR);
    $stmt->bindValue(':password', $pass, PDO::PARAM_STR);
    $stmt->execute();
    $result = $stmt->fetchAll();
    if($result)
    {
        //echo $msg = "user exist";
        if(session_status() == PHP_SESSION_NONE)
        {
            session_start();
            foreach($result as $row)
            {
                $hash = password_hash($row['user_pass'], PASSWORD_BCRYPT);
                if(password_verify($row['user_pass'], $hash))
                {
                    $_SESSION['userid'] = $row['user_id'];
                    $_SESSION['role'] = $row['user_role'];
                    header("Location: homepage.php");
                }
            } 
        }
    }
    else
    {
        $msg = "Wrong credentials";
        header("Location: login_page.php");
    }

And as you see I am already saving my password in database as MD5 and I am using $pass = md5($_POST['user_pass']); to verify if the text input by the user is equal to MD5 hash.

  1. Now my question is should I use the password_hash and password_verify as I am using in this script ? Or using MD5 would be enough ?

  2. And my second question is can I save passwords in database using the hash string result or it is okay to use the md5 one?

回答1:

Yes, you should migrate to the new API and never use MD5 for this purpose again, immediately.


If you're not using password_hash()/password_verify() and want to migrate your code to a more secure method, seamlessly:

  1. Add a column to your user accounts table, called legacy_password (or equivalent).
  2. Calculate the bcrypt hash of the existing MD5 hashes and store them in the database (setting legacy_password to TRUE).
  3. Modify your authentication code to handle the legacy flag.

When a user attempts to login, first check if the legacy_password flag is set. If it is, first pre-hash their password with MD5, then use this prehashed value in place of their password. Afterwards, recalculate the bcrypt hash and store the new hash in the database, disabling the legacy_password flag in the process. A very loose example in PHP 7+ follows:

/**
 * This is example code. Please feel free to use it for reference but don't just copy/paste it.
 *
 * @param string $username Unsafe user-supplied data: The username
 * @param string $password Unsafe user-supplied data: The password
 * @return int The primary key for that user account
 * @throws InvalidUserCredentialsException
 */
public function authenticate(string $username, string $password): int
{
    // Database lookup
    $stmt = $this->db->prepare("SELECT userid, passwordhash, legacy_password FROM user_accounts WHERE username = ?");
    $stmt->execute([$username]);
    $stored = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$stored) {
        // No such user, throw an exception
        throw new InvalidUserCredentialsException();
    }
    if ($stored['legacy_password']) {
        // This is the legacy password upgrade code
        if (password_verify(md5($password), $stored['passwordhash'])) {
            $newhash = password_hash($password, PASSWORD_DEFAULT);
            $stmt = $this->db->prepare("UPDATE user_accounts SET passwordhash = ?, legacy_password = FALSE WHERE userid = ?");
            $stmt->execute([$newhash, $stored['userid']]);

            // Return the user ID (integer)
            return $stored['userid'];
        }
    } elseif (password_verify($password, $stored['passwordhash'])) {
        // This is the general purpose upgrade code e.g. if a future version of PHP upgrades to Argon2
        if (password_needs_rehash($stored['passwordhash'], PASSWORD_DEFAULT)) {
            $newhash = password_hash($password, PASSWORD_DEFAULT);
            $stmt = $this->db->prepare("UPDATE user_accounts SET passwordhash = ? WHERE userid = ?");
            $stmt->execute([$newhash, $stored['userid']]);
        }
        // Return the user ID (integer)
        return $stored['userid'];
    }
    // When all else fails, throw an exception
    throw new InvalidUserCredentialsException();
}

Usage:

try {
    $userid = $this->authenticate($username, $password);
    // Update the session state
    // Redirect to the post-authentication landing page
} catch (InvalidUserCredentialsException $e) {
    // Log the failure
    // Redirect to the login form
}

Proactively upgrading legacy hashes is a security win over an opportunistic strategy (rehashing when the user logs in, but leave the insecure hashes in the database for inactive users): With a proactive strategy, if your server gets compromised before everyone logs in again, their passwords are already using an acceptable algorithm (bcrypt, in the example code).

The above example code is also available in Bcrypt-SHA-384 flavor.


Also, this has nothing to do with encryption.