-1

I am building a website's login page for an assignment. When I hash the password in the file that checks the users details it doesn't match with the stored hashed password in the database. The code always goes to the last else statement and relinks me to the login page with the wrong password sv equal to 1. If I don't hash the password, then copy and paste the hashed password from the database into the login form the login works. If anyone can help this would be greatly appreciated

        ini_set('display_errors', 1);
        ini_set('log_errors',1);
        error_reporting(E_ALL);
        mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
        session_start();

        $email = $_POST["email"];
        $pass1 = $_POST["pass"];
        $pass = hash('sha256', $pass1);

        if(isset($_SESSION['user_type']))
        {
            unset($_SESSION['user_type']);
        }

        include("group_detail.php");
        $query = "SELECT * from employee WHERE email = '$email' AND password = '$pass'";
        $result_employee = $db->query($query);
        $employee_row = mysqli_fetch_assoc($result_employee);

        if(!empty($employee_row)){

            $_SESSION['id'] = $employee_row['employee_ID'];
            $_SESSION['name'] = $employee_row['name'];
            $_SESSION['user_type'] = $employee_row['title'];
            header('Location: homepage.html');

        }else{

            $query = "SELECT * from customer WHERE email = '$email' AND password = '$pass'";
            $result_customer = $db->query($query);
            $customer_row = mysqli_fetch_assoc($result_customer);

            if(!empty($customer_row)){

                $_SESSION['id'] = $customer_row['customer_ID'];
                $_SESSION['name'] = $customer_row['name'];
                $_SESSION['user_type'] = 'Customer';
                $_SESSION['email'] = $customer_row['email'];
                header('Location: homepage.html');
            }

            else{

                $_SESSION['wrong_password'] = 1;
                header('Location: login.php');

            }
        }

The registration code

<<?php
        // this code checks all reuired fields are filled in appropriately
        ini_set('display_errors', 1);
        ini_set('log_errors',1);
        error_reporting(E_ALL);
        mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
        session_start();

        $nameErr = $phoneErr = $emailErr = $passwordErr = "";
        $name = $address = $eircode = $email = $password = $phone = "";
        $employee_ID = 0;

        function test_input($data) {
              $data = trim($data);
              $data = stripslashes($data);
              $data = htmlspecialchars($data);
              return $data;
            }

        if ($_SERVER["REQUEST_METHOD"] == "POST") {
          echo $nameErr;
          if (empty($_POST["name"])) {
            $nameErr = "Your name is required for registration";
          } else {
            $name = test_input($_POST["name"]);
            if (!preg_match("/^[a-zA-Z ]*$/",$name)) {
              $nameErr = "Only letters and a space allowed";
           }
          }

          if (empty($_POST["phone"])) {
            $phoneErr = "Your phone number is required for registration";
          } else {
            $phone = test_input($_POST["phone"]);
          }
          if(empty($_POST['email']))
          {
            $emailErr = "Your Email is required for registration";
          } else {
                include ("group_detail.php");
                $email_test = test_input($_POST["email"]);
                $sql = "SELECT * from customer WHERE email = '$email_test'";
                // Checks if another account uses this email
                            $result = $db->query($sql); // runs the query
                            $num_rows_3= mysqli_num_rows($result); // counts how many rows the query applies to
                if($num_rows_3 == 0){
                    // Sets email value if no one else has used this email to sign up before
                    $email = test_input($_POST["email"]);
                }
                else{
                    // Lets the customer know this email is already in use
                    $emailErr = "Another account has previously been registered with this email. If this is you, you can login ";
                }
          }
          if(empty($_POST['pass1']))
          {
            $passwordErr = "Password required";
          } else {
            $pass1 = $_POST['pass1'];
            $pass2 = $_POST['pass2'];
            if($pass1 == $pass2){
                $pass = hash('sha256',$pass1);
                // $pass = $pass1;
            } else{
                $passwordErr = "The passwords you enter must match";
            }
          }

          if(empty($_POST['address']))
          {
            $address = "";
          }else{
            $address = test_input($_POST['address']);
          }

          if(empty($_POST['eircode']))
          {
            $eircode = "";
          }else{
            $eircode = test_input($_POST['eircode']);
          }



          if ($phoneErr == "" && $nameErr == "" && $passwordErr == "" && $emailErr == "")
          {
            // This code enters the data from the form into the customer table
            include ("group_detail.php");

            $q  = "INSERT INTO customer(";
            $q .= "name, phone, password, email, address, eircode";
            $q .= ") VALUES (";
            $q .= "'$name', '$phone', '$pass', '$email', '$address', '$eircode')";

            $result = $db->query($q);

            $sql = "SELECT customer_ID FROM customer ORDER BY customer_ID DESC LIMIT 1";
            $result1 = $db->query($sql);
            $row = mysqli_fetch_assoc($result1);
            $_SESSION['customer'] = $row['customer_ID'];
            header('Location: homepage.html');
          }
        }

    ?>
Tom Grant
  • 11
  • 3
  • 1
    (Possible) side note: Do not use string interpolation or concatenation to get values into SQL queries. That's error prone and might make your program vulnerable to SQL injection attacks. Use parameterized queries. See ["How to include a PHP variable inside a MySQL statement"](https://stackoverflow.com/questions/7537377/how-to-include-a-php-variable-inside-a-mysql-statement) and ["How can I prevent SQL injection in PHP?"](https://stackoverflow.com/questions/60174/how-can-i-prevent-sql-injection-in-php). – sticky bit Feb 21 '21 at 23:27
  • You must not hash the password when the user submits the form to login, but when the user registers, that is, `$pass1 = $_POST["pass"];` is enough and `$pass = hash('sha256', $pass1);` it is inappropriate. –  Feb 21 '21 at 23:29
  • I'm wondering if something in `group_detail.php` might be interfering somehow. – Tangentially Perpendicular Feb 21 '21 at 23:29
  • I had realised that there might be an issue with conflict in group_detail.php but I have cast the password as pass to solve this – Tom Grant Feb 21 '21 at 23:32
  • so if I check an unhashed password it would be should match @Anne Batch? – Tom Grant Feb 21 '21 at 23:38
  • @TomGrant You should check the password that is in the `$_POST` super global variable with the hash that is in the database. –  Feb 21 '21 at 23:41
  • What's inside `group_detail.php`? –  Feb 21 '21 at 23:43
  • it is the login details for the mysql database that I am using – Tom Grant Feb 21 '21 at 23:46
  • You mean the connection to the database? –  Feb 21 '21 at 23:49
  • 1
    We need to see the registration code as well – Steven Feb 21 '21 at 23:55
  • var_dump($pass) and see if it's exactly like the password stored in the database – Raskul Feb 21 '21 at 23:57
  • 1
    we need the registration backend code, not the frontend code – Raskul Feb 21 '21 at 23:59
  • 2
    @SamRaskul it's probably better to encourage _prepared statements_ rather than using the `mysqli` function to escape the string? – Steven Feb 22 '21 at 00:01
  • 1
    I'm not sure what you mean by backend code @Sam Ruskal – Tom Grant Feb 22 '21 at 00:03
  • @Steven what's your suggestion to prevent SQL injection? – Raskul Feb 22 '21 at 00:03
  • @TomGrant the PHP codes for registration, the codes that execute after the user register on your site, how does it hash the password and store it to the database? – Raskul Feb 22 '21 at 00:07
  • 2
    @SamRaskul Much like you said that `password_hash` is preferential for hashing passwords in PHP... _**Prepared Statements**_ are the standard for preventing SQL injection; using `mysqli | PDO`. – Steven Feb 22 '21 at 00:08
  • What's the field size in the db? – Steven Feb 22 '21 at 00:17
  • its 15 characters long @Steven – Tom Grant Feb 22 '21 at 00:19
  • 1
    Okay, then you need to change it to something in excess of `varchar(64)`. SHA256 will produce a hash length of 64 characters. update the DB structure, then re-register and try again – Steven Feb 22 '21 at 00:20

1 Answers1

2

Solution

Your field is of the incorrect length. When you use the SHA256 hash function you get an output similar to:

ef92b778bafe771e89245b89ecbc08a44a4e166c06659911881f383d4473e94f    // password123

If you're password field is only 15 characters then the saved value will be truncated:

ef92b778bafe771

However, during the comparison the full value from the logon script is used against the truncated version stored in the DB and therefore there is no match. Because, as you can see above, they aren't the same.

To fix you need to ALTER the table so that the field is at least varchar(64). Then new accounts will work as expected (note: old hashes still won't work - they need to be redone!)


Additional information

There are a few other issues with your code...

  1. You shouldn't be putting variables directly into your code. Instead it is preferred to use a Prepared Statement with parametrised queries where you bind the variables later.
    • Which basically means in the query we use a place holder ? where we want a variable and then bind variables to the place holders later on
    • This is mainly to prevent SQL injection and protect you from incorrect input
  2. It is best to use the PHP built in functions password_* to hash and verify passwords.
    • It's more secure than simply using hash
    • salts are auto-generated which protects you from things like rainbow tables
    • The default algorithm for password_hash requires a field length of 60+ characters
  3. There's no need to store excess data in SESSION
    • The data is already stored in the DB so just fetch it as and when needed
  4. It seems that you have one table for customers and another for employees
    • This isn't a good design there should be one table for users and then you can set flags for employee, customer, supplier etc.
  5. Your test_input function carries out functions that are usually done on display not on save.

Below is a quick re-write that addresses some of the above (note: the below code is not complete it doesn't, for example, carry out all of the same validation - e.g. checking for illegal characters - it's just for illustrative purposes)

Register

<?php

ini_set('display_errors', true);
ini_set('log_errors', true);
error_reporting(E_ALL);
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

session_start();

$errors = [];

$name    = $_POST["name"]    ?? null;
$phone   = $_POST["phone"]   ?? null;
$email   = $_POST['email']   ?? null;
$address = $_POST['address'] ?? null;
$eircode = $_POST['eircode'] ?? null;
$pass1   = $_POST['pass1']   ?? null;
$pass2   = $_POST['pass2']   ?? null;


// Check passwords are the same and assign hash to $pass
$pass = $pass1 === $pass2 ? password_hash($pass1, PASSWORD_DEFAULT) : null;


// Check the required fields are present and not empty
if (!$name || !$phone || !$email || !$pass) {
    $errors[] = "Required fields are missing.";
}


// Check if the email address already exists in the DB
$checkEmailExistsSQL   = "SELECT COUNT(*) as countEmails FROM user WHERE email = ?";
$checkEmailExistsQuery = $mysqli->prepare($checkEmailExistsSQL);
$checkEmailExistsQuery->bind_param("s", $email);
$checkEmailExistsQuery->execute();

$emailExists = $checkEmailExistsQuery->get_result()->fetch_assoc()["countEmails"];

if ($emailExists !== 0) {
    $errors[] = "The email address already exists in the DB";
}


// Check if there were errors and output them; then exit the script
if (count($errors)) {
    foreach($errors as $error) {
        echo $error, PHP_EOL;
    }
    exit;
}

include("group_detail.php");

$insertSQL = "
    INSERT INTO user
        (name, phone, password, email, address, eircode)
    VALUES
        (?, ?, ?, ?, ?, ?)
";

$insertQuery = $mysqli->prepare($insertSQL);
$insertQuery->bind_param("ssssss", $name, $phone, $pass, $email, $address, $eircode);
$insertQuery->execute();

// Success the user is registered

Logon

<?php

ini_set('display_errors', true);
ini_set('log_errors', true);
error_reporting(E_ALL);
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

session_start();

$email = $_POST["email"] ?? null;
$pass  = $_POST["pass"]  ?? null;

// You can remove the old user id. But you don't need to
// 
// There's no need to store excess data on the user in
// the SESSION super global; any data that you need
// access to can be retrieved from the DB at will.
// Copying data into SESSION only eats into memory.
unset($_SESSION["id"]);

// Check that something was submitted for email and password
if (!$email || !$pass) {
    echo "Error: all fields need to be completed";
    exit;
}

include("group_detail.php");

$sql   = "SELECT id, password FROM user WHERE email = ?";
$query = $mysqli->prepare($sql);
$query->bind_param("s", $email);
$query->execute();

// Check to see if the email address is registered.
// Then check to see if the password is a match.
if (
    !($user = $query->get_result()->fetch_assoc())
    || !password_verify($pass, $user["password"])
) {
    echo "Error: the email address or password isn't correct";
    exit;
}


// Success the user is logged on
// 

$_SESSION["id"] = $user["id"];
Steven
  • 6,053
  • 2
  • 16
  • 28