5

I grabbed the code for defining the bcrypt function from https://stackoverflow.com/a/6337021/2115954. The registration of the password works fine, and saves all of the fields to the table in the database. The problem is that the password_login will not work because when it is hashed, and the salt is added, it adds a different salt. What is the problem here, and how can I fix it.

Things I've Tried

  • tried getting rid of new from new bcrypt
  • adding $salt to hash('$password_login', $salt) in both login script and register scripts
  • searching for similar situations as mine, and all I have found are questions/topics about comparing the hashed $password_login and the stored, hashed $pswd together
  • also added echo "$hash", echo "$isGood" to determine if they are being verified and what they look like without going to the db to look for them

login script in index.php

<?
//Login Script
if (isset($_POST["user_login"]) && isset($_POST["password_login"])) {
    $user_login = (!empty($_POST['user_login'])) ? $_POST['user_login'] : ''; // filter everything but numbers and letters
    $password_login = (!empty($_POST['password_login'])) ? $_POST['password_login'] : ''; // filter everything but numbers and letters 
        $bcrypt = new Bcrypt(10);

        $hash = $bcrypt->hash('$password_login', $salt);
        $isGood = $bcrypt->verify('$password_login', $hash);
        echo "$hash";
        exit();
    $db = new PDO('mysql:host=localhost;dbname=socialnetwork', 'root', 'abc123');
    $db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
    $sql = $db->prepare("SELECT id FROM users WHERE username = :user_login AND password = :password_login LIMIT 1");
    if ($sql->execute(array(
    ':user_login' => $user_login,
    ':password_login' => $hash))) {
        if ($sql->rowCount() > 0){
            while($row = $sql->fetch()){
                $id = $row["id"];
            }
            $_SESSION["id"] = $id;
            $_SESSION["user_login"] = $user_login;
            $_SESSION["password_login"] = $hash;
        } else {
            echo 'Either the password or username you have entered is incorrect. Please check them and try again!';
            exit();
        }
    }
}
?>

register script in index.php

<?
class Bcrypt {
  private $rounds;
  public function __construct($rounds = 12) {
    if(CRYPT_BLOWFISH != 1) {
      throw new Exception("bcrypt not supported in this installation. See http://php.net/crypt");
    }

    $this->rounds = $rounds;
  }

  public function hash($input) {
    $hash = crypt($input, $this->getSalt());

    if(strlen($hash) > 13)
      return $hash;

    return false;
  }

  public function verify($input, $existingHash) {
    $hash = crypt($input, $existingHash);

    return $hash === $existingHash;
  }

  private function getSalt() {
    $salt = sprintf('$2a$%02d$', $this->rounds);

    $bytes = $this->getRandomBytes(16);

    $salt .= $this->encodeBytes($bytes);

    return $salt;
  }

  private $randomState;
  private function getRandomBytes($count) {
    $bytes = '';

    if(function_exists('openssl_random_pseudo_bytes') &&
        (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL slow on Win
      $bytes = openssl_random_pseudo_bytes($count);
    }

    if($bytes === '' && is_readable('/dev/urandom') &&
       ($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) {
      $bytes = fread($hRand, $count);
      fclose($hRand);
    }

    if(strlen($bytes) < $count) {
      $bytes = '';

      if($this->randomState === null) {
        $this->randomState = microtime();
        if(function_exists('getmypid')) {
          $this->randomState .= getmypid();
        }
      }

      for($i = 0; $i < $count; $i += 16) {
        $this->randomState = md5(microtime() . $this->randomState);

        if (PHP_VERSION >= '5') {
          $bytes .= md5($this->randomState, true);
        } else {
          $bytes .= pack('H*', md5($this->randomState));
        }
      }

      $bytes = substr($bytes, 0, $count);
    }

    return $bytes;
  }

  private function encodeBytes($input) {
    // The following is code from the PHP Password Hashing Framework
    $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

    $output = '';
    $i = 0;
    do {
      $c1 = ord($input[$i++]);
      $output .= $itoa64[$c1 >> 2];
      $c1 = ($c1 & 0x03) << 4;
      if ($i >= 16) {
        $output .= $itoa64[$c1];
        break;
      }

      $c2 = ord($input[$i++]);
      $c1 |= $c2 >> 4;
      $output .= $itoa64[$c1];
      $c1 = ($c2 & 0x0f) << 2;

      $c2 = ord($input[$i++]);
      $c1 |= $c2 >> 6;
      $output .= $itoa64[$c1];
      $output .= $itoa64[$c2 & 0x3f];
    } while (1);

    return $output;
  }
}
$reg = @$_POST['reg'];
//declaring variables to prevent errors
//registration form
$fn = (!empty($_POST['fname'])) ? $_POST['fname'] : '';
$ln = (!empty($_POST['lname'])) ? $_POST['lname'] : '';
$un = (!empty($_POST['username'])) ? $_POST['username'] : '';
$em = (!empty($_POST['email'])) ? $_POST['email'] : '';
$em2 = (!empty($_POST['email2'])) ? $_POST['email2'] : '';
$pswd = (!empty($_POST['password'])) ? $_POST['password'] : '';
$pswd2 = (!empty($_POST['password2'])) ? $_POST['password2'] : '';
$d = date("y-m-d"); // Year - Month - Day

if ($reg) {
    if ($em==$em2) {
        // Check if user already exists
        $statement = $db->prepare('SELECT username FROM users WHERE username = :username');
            if ($statement->execute(array(':username' => $un))) {
                if ($statement->rowCount() > 0){
                    //user exists
                    echo "Username already exists, please choose another user name.";
                    exit();
                }
            }
                    //check all of the fields have been filled in
                        if ($fn&&$ln&&$un&&$em&&$em2&&$pswd&&$pswd2) {
                            //check that passwords match
                                if ($pswd==$pswd2) {
                                    //check the maximum length of username/first name/last name does not exceed 25 characters
                                        if (strlen($un)>25||strlen($fn)>25||strlen($ln)>25) {
                                            echo "The maximum limit for username/first name/last name is 25 characters!";
                                        }
                                        else
                                            {
                                                //check the length of the password is between 5 and 30 characters long
                                                    if (strlen($pswd)>30||strlen($pswd)<5) {
                                                        echo "Your password must be between 5 and 30 characters long!";
                                                    }
                                                    else
                                                        {
                                                            //encrypt password and password 2 using md5 before sending to database
                                                                $bcrypt = new Bcrypt(10);

$hash = $bcrypt->hash('$pswd', $salt);
echo "<p>$hash</p>";
$isGood = $bcrypt->verify('$pswd', $hash);
echo "<p>$isGood</p>";

                                                                $bcrypt2 = new Bcrypt(10);

$hash2 = $bcrypt2->hash('$pswd2', $salt);
echo "<p>$hash2</p>";
$isGood2 = $bcrypt2->verify('$pswd2', $hash2);
echo "<p>$isGood2</p>";
exit();                                                             
                                                                $db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
                                                                $sql = 'INSERT INTO users (username, first_name, last_name, email, password, sign_up_date)';
                                                                $sql .= 'VALUES (:username, :first_name, :last_name, :email, :password, :sign_up_date)';

                                                                $query=$db->prepare($sql);

                                                                $query->bindParam(':username', $un, PDO::PARAM_STR);
                                                                $query->bindParam(':first_name', $fn, PDO::PARAM_STR);
                                                                $query->bindParam(':last_name', $ln, PDO::PARAM_STR);
                                                                $query->bindParam(':email', $em, PDO::PARAM_STR);
                                                                $query->bindParam(':password', $hash, PDO::PARAM_STR);
                                                                $query->bindParam(':sign_up_date', $d, PDO::PARAM_STR);

                                                                $query->execute();

                                                                die("<h2>Welcome to Rebel Connect</h2>Login to your account to get started.");
                                                        }
                                            }
                                }
                                else {
                                    echo "Your passwords do not match!";
                                }
                        }
                else
                    {
                        echo "Please fill in all fields!";
                    }
            }
    else {
        echo "Your e-mails don't match!";
    }
}
?>

Updated Login Script

<?
//Login Script
if (isset($_POST["user_login"]) && isset($_POST["password_login"])) {
    $user_login = (!empty($_POST['user_login'])) ? $_POST['user_login'] : ''; // filter everything but numbers and letters
    $password_login = (!empty($_POST['password_login'])) ? $_POST['password_login'] : ''; // filter everything but numbers and letters 
    $db = new PDO('mysql:host=localhost;dbname=socialnetwork', 'root', 'abc123');
    $db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
    $sql = $db->prepare("SELECT id, password FROM users WHERE username = :user_login LIMIT 1");
    $dbhash = $db->prepare("SELECT password FROM users WHERE username = :user_login LIMIT 1");
    $isGood = $bcrypt->verify('$_POST["password_login"]', $dbhash);
    echo "$isGood";
    exit(); //here for debugging purposes
    if ($sql->execute(array(
    ':user_login' => $user_login,
    ':password_login' => $hash))) {
        if ($sql->rowCount() > 0){
            while($row = $sql->fetch()){
                $id = $row["id"];
            }
            $_SESSION["id"] = $id;
            $_SESSION["user_login"] = $user_login;
            $_SESSION["password_login"] = $hash;
        } else {
            echo 'Either the password or username you have entered is incorrect. Please check them and try again!';
            exit();
        }
    }
}
?>

Update: I figured out where to place the code somewhat. The problem I'm having now is that it isn't redirecting to the home page, but I think that that is caused by not having the page refresh. Originally I had it automatically refresh upon the session starting and the user logging in. I'll update the code again to show changes made when I get home. If I reload the page, it does go to home.php

Community
  • 1
  • 1
cbenn95
  • 97
  • 2
  • 8
  • What version of PHP are you using? If you're using PHP >= 5.5 , PHP has the ability to hash passwords using Bcrypt built-in (see the docs http://php.net/password_hash ). That might make life a bit easier for you if you're able to use it instead of your own implementation. – Liv Mar 06 '13 at 18:38
  • I'm using 5.3.5? I know for sure it is 5.3 – cbenn95 Mar 06 '13 at 18:39
  • that happened to me as well. And I got the script from that link – samayo Mar 06 '13 at 18:42
  • @Liv I doubt many people use PHP 5.5 yet :P You know... considering it isn't released yet – PeeHaa Mar 06 '13 at 18:43
  • I'll keep posting edits to what I have tried, and updating the code every so once in a while, but I'm in high school and have classes right now, not to mention I'm also just now getting really into coding. (I'm learning most of the syntax and understanding of languages by myself with help from SO when needed). – cbenn95 Mar 06 '13 at 18:46

1 Answers1

7

Incorrect:

$hash = $bcrypt->hash('$password_login', $salt);
$isGood = $bcrypt->verify('$password_login', $hash);

Do not feed in a fresh hash to verify(), feed in the existing hash retrieved from the database.

eg: $b->verify('password', '$2a$12$9bAvuaBrMlWmw8oI9flz9e6pjJniKVpRyr9Fz0ScQVwMJA53R3uQO');

Also, the hash() function takes only one argument and generates its own random salt.

The flow of your login script should be:

  1. User provides username/password.
  2. SELECT id, password FROM users WHERE username = ?
  3. $isGood = $bcrypt->verify($password_login, $hash_from_db);
  4. ???
  5. Profit!

edit

What verify()/crypt() looks at in the background, aka what means what in the stored hash

note: in base64 encoding each character represents 6 bits,

$ 2a $ 12 $ 9bAvuaBrMl W mw8oI9flz9e6pjJniKVpRyr9Fz0ScQVwMJA53R3uQO
  |    |    |          | |
  |    |    |          | > the rest of the salted, hashed password
  |    |    |          > this character is split, the first 4 bits are part
  |    |    |            of the salt, the last 2 are part of the hash
  |    |    > the salt
  |    > the number of hashing 'rounds' are performed
  > Scheme ID, 2a is blowfish
Sammitch
  • 30,782
  • 7
  • 50
  • 77
  • I was just testing through trial and error :B thank you though. I had the idea though. I knew it was rehashing, just didn't know how to stop the rehash. So in the register script, I need to replace `$hash` with `$hash_from_db`? – cbenn95 Mar 06 '13 at 19:35
  • @cbenn95 no, it is the login script that needs to be changed. The database query needs to be run *before* verifying the hash. – Sammitch Mar 06 '13 at 19:38
  • okay, I thought about my comment before you had replied, and had that basis of your reply. What I need to do is in the login script, for verification, fetch the password based upon database username, and then compare it with the one provided from the password login hash – cbenn95 Mar 06 '13 at 19:44
  • @cbenn95 Yes. The salt for the hash is packed into the first 64 bits of the hash, `verify()`/`crypt()` parse out the necessary values to re-compute the proper hash. – Sammitch Mar 06 '13 at 20:10
  • I figured out where it needed to be placed through some trial an error. No need for you to check the code anymore, unless you have nothing to do. Hopefully our posts will help other having these problems :) I'd vote your answer up if I could, but currently I can't. – cbenn95 Mar 06 '13 at 21:43