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
new
from new bcrypt
$salt
to hash('$password_login', $salt)
in both login script and register scripts$password_login
and the stored, hashed $pswd
togetherecho "$hash"
, echo "$isGood"
to determine if they are being verified and what they look like without going to the db to look for themlogin 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
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:
SELECT id, password FROM users WHERE username = ?
$isGood = $bcrypt->verify($password_login, $hash_from_db);
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With