Password_verify

Hi, I realise this topic is everywhere but my question may be slightly different.

I had been using password_hash/password_verify with no issues at all. I moved host and everything working fine…except password_hash/password_verify. Both hosts mySql, and tables exactly the same. No change to php or the app that calls them. The only difference is previous PHP version was 7.4.30 and new host is 8.0.24.

Password are stored as VarChar(255) same as before with same collation. Literally nothing has changed so can only assume it has something to do with PHP version.

Does anyone know what this could be? Result is always false. The function is working eg, (password_verify(‘aaaaaa’, password_hash(‘aaaaaa’, PASSWORD_DEFAULT))) returns true), and can verify the correct hash is being checked against correct password entered and nothing sanitized in between.

Any help appreciated :confused:

Jamie

I suspect some difference with either the database layer or the processing of form data is causing the problem. There has been a minor change with the salt operation with password_hash(), that would affect if you are generating your own salt and storing it and using it separately from the salt in the generated hash value.

Have you verified that your database operations are doing what you expect and storing the actual hashed value from password_hash() and retrieving the actual stored value for use with password_verify() and that the entered password value being used as the first parameter with password_verify() is the actual entered value, e.g. not an empty or somehow modified value?

Beyond this, you would need to post all the relevant code needed to reproduce the registration and login processes so that anyone could determine if it is doing something that has changed that could affect the operation.

Hi,

Thanks your response.

Verified hash being generated is what is stored.
Verified password value entered is what is being used in password_verify.

There is some authentication done first but for pulling entered user/pass but below is summary. Its definitely comparing the correct values, but is almost as if the password_verify decrypts to something different than the hash the original password created (but only since moving host).

  $user = $_POST['theUser'];
  $pass = $_POST['thePass'];

  $stmt = $db->prepare("SELECT pass FROM theTable WHERE username=?");
  $stmt->bind_param("s", $user);
  $stmt->execute();
  $result = $stmt->get_result();
     while($row = $result->fetch_assoc()) {
     $hashedPass = $row["pass"];
     }

     if (password_verify($pass, $hashedPass)) { 
        echo 'true';
     }
    else 
     {
         echo 'false';
     }

One point on your code example…

There should only be one result for a username, therefore a loop is not needed.

What is the code that inserts the password?

The posted code isn’t even checking if a row of data was fetched, i.e. meaning that the username was found, before trying to use the fetched row of data.

1 Like

Hi benanamen,

Yes I know, it was just how I got it to work initially and left it as couldn’t find how to get single result, but username unique anyway (how should I do this if always only one result?). Below is insert. Again auth and db connection already made at this point.

  $device = $_POST['PhoneID'];
  $username = $_POST['UName'];
  $pass = $_POST['UPass'];
  $email = $_POST['email'];
  $country = $_POST['country'];
  $regKey = $_POST['rKey'];
  $password = password_hash($pass, PASSWORD_DEFAULT);
    
   $stmt = $db->prepare("INSERT INTO theTable (deviceID, username, pass, email, regKey, country) VALUES(?, ?, ?, ?, ?, ?)");
    $stmt->bind_param("ssssss", $device, $username, $password, $email, $regKey, $country);
    $stmt->execute();
    $result = $stmt->affected_rows;
    if ($result == 1)
    {
        echo "true";
    }
    else
    {
        echo "false";
    }
     $result->free();

I have checked and the hash that is generated is what goes in and that same hash is used to compare. I have started looking at other ways around this even though this was working fine for months on other server.

Jamie

Hi,

I thought the loop just wouldn’t run if no data found?

Is that not the case and this could cause problems?

Jamie

The loop won’t run. Which will result in $hashedPass being empty, therefore, password_verify() will fail. Your logic must detect if the query did/didn’t match a row of data, setting up a generic ‘incorrect username/password’ message if it didn’t match a row of data, and only verifying the password if it did match a row of data, setting up the same generic failure message if the password fails to verify.

Given the deficient logic being used in the login script, I’m going to guess that the apparent successful operation on one system was actually a false positive, probably combined with php’s output_buffing being on, hiding problems, and maybe even no exit/die statement following redirects, contributing its own symptoms.

Posting all the relevant code needed to reproduce the problem meant everything - the complete and actual forms and form processing code, and even the login check logic used on protected pages, everything except your database credentials, so that someone could help you. By not including this information, you have not eliminated any similar logic mistakes/misunderstandings in all this code that could be resulting in the incorrect operation on one system or the ‘apparently’ successful operation on the other system. Posting your database table definition would help as well.

Hi phdr,

All noted and appreciated. However just to note, in the example, the correct hash is returned and used in password_verify with the correct username.

If no rows are found it returns false.

Below is full user add where hash is generated/added:

  require_once('checkKey.php');
  $u = $_POST['uKey'];
  $p = $_POST['pKey'];
  $device = $_POST['PhoneID'];
  $username = $_POST['UName'];
  $pass = $_POST['UPass'];
  $email = $_POST['email'];
  $country = $_POST['country'];
  $regKey = $_POST['rKey'];
  $password;
  $auth = checkAuth($u, $p);

  if ($auth == "false")
  {
    echo "FORBIDDEN";
    exit;
  }


  $curDir = getcwd();
  require_once($curDir. '/config/mysqli.php');
  $db = connectMe();
   if ($db) 
   {           
    $password = password_hash($pass, PASSWORD_DEFAULT);
    $stmt = $db->prepare("INSERT INTO TableReactionUsers (deviceID, username, pass, email, regKey, country) VALUES(?, ?, ?, ?, ?, ?)");
    $stmt->bind_param("ssssss", $device, $username, $password, $email, $regKey, $country);
    $stmt->execute();
    $result = $stmt->affected_rows;
    if ($result == 1)
    {
        echo "true";
    }
    else
    {
        echo "false";
    }
     
     
     $result->free();
 
   }
   else 
   {
      echo "NOT CONNECTED";
   }

And below is login check:

  $u = $_POST['uKey'];
  $p = $_POST['pKey'];

  $user = $_POST['theUser'];
  $pass = $_POST['thePass'];

  $auth = checkAuth($u, $p);
  
  if ($auth == "false")
  {
            echo "FORBIDDEN";
    exit;
  }

  $curDir = getcwd(); 
  require_once($curDir. '/config/mysqli.php');
  $db = connectMe();
    if ($db) {
     $stmt = $db->prepare("SELECT pass FROM TableReactionUsers WHERE username=?");
     $stmt->bind_param("s", $user);
     $stmt->execute();
     $result = $stmt->get_result();
     $rowsFound = $result->num_rows;
    
     while($row = $result->fetch_assoc()) {
     $hashedPass = $row['pass'];
     }

     ///NOTE: THIS WILL SHOW CORRECT PASS & HASH echo $pass. ' ' .$hashedPass;
  
     if (password_verify($pass, $hashedPass)) {
        echo 'true';
     }
    else 
     {
         echo 'false';
     }
 
   } 
   else
   {
      echo "NOT CONNECTED";
   }

Table:

CREATE TABLE TableReactionUsers (
ID int(11) NOT NULL,
deviceID varchar(50) COLLATE utf8_unicode_ci NOT NULL,
username varchar(50) COLLATE utf8_unicode_ci NOT NULL,
pass varchar(255) COLLATE utf8_unicode_ci NOT NULL,
email varchar(100) COLLATE utf8_unicode_ci NOT NULL,
regkey varchar(20) COLLATE utf8_unicode_ci NOT NULL,
country varchar(25) COLLATE utf8_unicode_ci NOT NULL,
changePassKey varchar(255) COLLATE utf8_unicode_ci NOT NULL,
Expiry datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Jamie

Is that really all the code? What is or where’s the code testing if a post method form has been submitted before using the form data? Where’s the code trimming the input data before validating it? Where’s the code validating the inputs before using them, so that you aren’t for example hashing an empty string and storing that hash? What does the login code do to remember who the successfully logged in user is? It should be storing the user id (autoincrement primary index) in a session variable upon successfully determining if the username matches and the password verifies. What is or where’s the code testing for a logged in user on protected pages?

Also, I’m wondering what the checkAuth() is doing, but if it is returning a boolean false value upon failure, that is not the same as the string ‘false’, which is a true boolean value, so those comparisons will never fail and echo the forbidden message and exit;

Hi phdr,

May be looking into this a bit too deeply. Its just the password_verify function that the issue is with (I am not saying everything else is ideal but it is fine for its purpose)

The php is called from an app. The checkAuth essentially confirms its from the app.
I understand there should be checks there for empty string but in this case its not important.
The login code returns true if pass matches the pass for that user or false otherwise. It then lets them into the app. The app then stores user info locally so they don’t have to log in again…unless they log out or change device.

Its just simply there to check if the user/pass inputted on the app is a valid user/pass pair.

As I said, I can confirm that the hash entered onto database is the hash created and I can also confirm the hash pulled from the database is the correct one pulled from the database and is comparing with the correct password string that was used to generate the hash in the first place.

I didn’t just jump onto a forum to ask this question. I spent days looking/testing to see what had gone wrong. I assumed it had something to do with the change in host as nothing else had changed and hoped someone had come across this before.

Because you haven’t supplied everything needed to reproduce the problem, you haven’t narrowed down the problem to any particular part of the operation. Until you find what’s causing the problem, everything is important and there are just too many different possibilities at this point. You could for example have a comparison between values using one =, an assignment operator, rather than two ==, a comparison operator, resulting in the wrong result from that comparison.

Based on the two logic issues in the posted code, e.g. not checking if the username was matched before testing the hash and comparing a returned value from a function with the string containing the characters f, a, l. s, and e, which is not a boolean false value, you could have other similar mistakes that could be causing the (apparent) successful operation on one system and failed operation on another.

In looking at the posted information more closely, the error handling for your database connection should be at the point of the making the connection (not in the main application code), do you have any error handling for the prepare() and execute() calls (they can both fail with errors), you are not including the user id column in the login select query (which should be the value that gets stored in a session variable to indicate who the logged in user is), and the username and email columns should be defined as unique indexes in the database table so that your database enforces uniqueness (the error handling for the insert query would then need to test for a duplicate index error number, query to find which column(s) contained duplicate values, and setup error messages for the user telling them what was wrong with the data that they submitted.)

Some of the inconstant symptoms could be the result of multiple http requests, on one system but not the other, but since you haven’t shown everything, no one here can either eliminate this as one of the possible causes, or provide more concentrated troubleshooting steps to test if it is.

And another hint of what could be occurring, that you haven’t provided any way to prove or disprove, you could be having a character set conversion occurring over the database connection, so that any hashed value is being changed, but would ‘display’ properly when echoed. Are you even setting the character set, to match your database tables, when you make the database connection?

Hi, just so you know, I have now solved this and it had nothing to do with password_verify or any of the PHP. But your reply prompted me to have better checks in place for the $POST variables and this led me to the answer. Lesson learned. Thank you.

Sponsor our Newsletter | Privacy Policy | Terms of Service