Simple login script with sha256 hashing I have been using this code for a while. I got it from a tutorial 5 years back. Now I'm wondering if it still is secure, or if it's time to find a new one? Maybe it has never been secure enough.

LOGIN

&lt;?php 
//Connect to database
require("include/config.php");

//Empty Variable
$submitted_username = '';

//Check if something is posted
if(!empty($_POST)){ 

    //Check if user exists
    $query = "SELECT * FROM usrs WHERE usr_email = :usr_email";
    $query_params = array(':usr_email' =&gt; $_POST['usr_email']); 
    try{ 
        $stmt = $db-&gt;prepare($query); 
        $result = $stmt-&gt;execute($query_params); 
    }
    catch(PDOException $ex){ die("Failed to run query: " . $ex-&gt;getMessage()); } 

    //Fetch result
    $row = $stmt-&gt;fetch(); 

    //Reset var
    $login_ok = false;

    //If usr exists, check password
    if($row){
        //Check if password matches
        $check_password = hash('sha256', $_POST['usr_password'] . $row['usr_salt']); 
        for($round = 0; $round &lt; 65536; $round++){
            $check_password = hash('sha256', $check_password . $row['usr_salt']);
        }
        if($check_password === $row['usr_password']){
            $login_ok = true;
        } 
    }

//LOGIN OK
    if($login_ok){ 
        //UNSET VARS
        unset($row['usr_salt']); 
        unset($row['usr_password']);

        //LOG USR LOGIN DATA
        $dateTime = date('Y-m-d H:i:s');
        //Create array
        $usr_data = array(
            ':usr_id' =&gt; $row['id'],
            ':usr_name' =&gt; $row['usr_fname']. " " . $row['usr_lname'],
            ':dateTime' =&gt; $dateTime,
            ':ip' =&gt; $_SERVER['REMOTE_ADDR']
        );


        //CREATE SESSION 
        $_SESSION['usr'] = $row;

        //REDIRECT TO STARTPAGE
        header("Location: start.php");

    } 
    else{ 
        echo "&lt;script type='text/javascript'&gt;alert('Fel uppgifter..');&lt;/script&gt;"; 
        $submitted_username = htmlentities($_POST['usr_email'], ENT_QUOTES, 'UTF-8'); 
    } 
} 
?&gt;


CONFIG

$options = array(PDO::MYSQL_ATTR_INIT_COMMAND =&gt; 'SET NAMES utf8'); 
try { $db = new PDO("mysql:host={$host};dbname={$dbname};charset=utf8", $username, $password, $options); } 
catch(PDOException $ex){ die("Failed to connect to the database: " . $ex-&gt;getMessage());} 
$db-&gt;setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
$db-&gt;setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); 

//SET HEADER
header('Content-Type: text/html; charset=utf-8'); 

//START SESSION
if(!isset($_SESSION)){ 
    session_start([
        'cookie_lifetime' =&gt; 86400,
    ]);
}

//SET TIMEZONE
date_default_timezone_set('Europe/Stockholm'); 
?&gt;


CHECK IF LOGGED IN

&lt;?php

//CHECK IF USR IS LOGGED IN
if(empty($_SESSION['user'])){
    echo "&lt;script&gt;window.location = '/index.php'&lt;/script&gt;";
    die("Redirecting to index.php"); 
}
There's a timing attack. To see whether a user has registered or not, I can try to login. If it fails fast, the user is not registered. If it takes time, the user is registered. To fix this information disclosure, calculate some dummy hash even if the user cannot be found in the database.

The SQL part is fine.

The $submitted_username calls htmlentities too early. The only correct time to call htmlentities is exactly at the point where you embed a text into an HTML snippet. That doesn't happen in your code, though. To fix this, rename the variable from submitted_username to submitted_username_html, to prevent it from being used in any other context. For example, code that reads db_insert($submitted_username_html) looks wrong enough to warrant a thorough code review.

Your code could benefit from being split into a few well-named functions. After doing that, the main code might read like this:

$logged_in_user = log_in($_POST);
if ($logged_in_user === FALSE) {
    // …
} else {
    // …
}


When you extract all the detailed code into a function, you can use early returns for all the error cases. And when you step through the code using a debugger, you can easily skip over all the details of the login process, if you are not interested in it.

In the config part, you should prefix all variable names with db_, to avoid confusing the username with the submitted_username. Or group them into an object called db_config. Then you can access it as $db_config['username']. There, the identifier username is appropriate since it is qualified by the word db_config, which makes it unambiguous.

The SHA-256 algorithm you use for hashing is not secure enough anymore since specialized hardware can compute it too fast, even with 65536 iterations. PHP has a built-in set of password hashing algorithms. Just use that instead of iterating on your own. Migrating from your custom password hashing to the PHP default will cost a bit of time and work, but it's worth the effort. And since you are not the first to do that, there's already plenty of documentation on it. Probably. Hopefully.