Search code examples
phpmysqlhashphp-password-hash

Efficiently checking a hashed password from database


firstly I have tried my best to find a definitive answer on this. Secondly, my code appears to work, but I want to confirm I am doing this in an efficient manner and not leaving myself open to security breaches.

Firstly, I use PHP password_hash when adding user to admin table;

$stmt = $dbh->prepare("INSERT INTO admin (username, password) VALUES (:username, :password)");
$stmt->bindParam(':username', $username);
$stmt->bindParam(':password', $password);

$password = password_hash('password', PASSWORD_DEFAULT);

Secondly, when a user attempts to login, I retrieve users from the admin table by matching the username ONLY, as I couldn't see a way to check the hash during the query (this is the part I am unsure if there is a better way), and also define the $password variable from the POST input;

$stmt = $dbh->prepare("SELECT * FROM admin WHERE username = :username");
    $stmt->bindParam(':username', $username);
    $username = $_POST['username'];
    // define $password for use in password verify
    $password = $_POST['password'];

Thirdly, if there is a result from the query, I run password_verify on the user input to check for a match, and then I branch depending on true or false.

if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            if (password_verify($password, $row['password'])) {
                session_start();
                foreach ($row as $user) {
                    $_SESSION['user'] = $row['id'];
                }
            } else {
                $errors = true;
            }                       
            header('location: leads.php');
        }
        else {
            $errors = true;
        }

I know there are many different ways to hash / secure passwords, but using the native password_hash functions is the way I decided to go, my question is have I done it right / is there a better way?

Thanks in advance.


Solution

  • Basically, your code looks quite okay, except the things already mentioned. As you ask for improvements, especially performance related ones, here we go:

    • Add an index on username
    • If your table gets a lot of entries, remove the index from username, add a new column called hash with an index and rewrite your insert and select like this:

      INSERT INTO admin (username, password, hash) VALUES (:username, :password, crc32(username))

      SELECT * FROM admin WHERE username = :username AND hash=crc32(:username)

    I assume, you use MySQL, so adding LIMIT 1 to your query helps the optimizer and stops searching after the row is found.

    Avoiding the foreach-loop is also possible, if you work with just one row.

    BTW: header('location: leads.php'); should read header('Location: leads.php'); and using absolute paths makes things more robust.