Password Hashing SELECT (PHP) - hash

Is it possible to select a hashed and salted password from MySql DB only using the posted password? If so, how?
If I hash the password this way:
$password = "blabla";
$hash = password_hash($password, PASSWORD_DEFAULT);
$hash will be, for example, $2y$10$8zzd3lj6oIPlBPnCxsU7nOmtsEFlKw/BdqTXyMgbuojjVpiEe4rVm and it will be stored in the db.
How, during a login, do I check against the hashed password column only, and only table's column, if the passwords match, having only 'blabla' as data?

A properly salted and hashed password cannot be searched for with a database query. You will have to search for the hash by username/email/... and afterwards you can verify the entered password with the found hash.
1) First query for the stored hash
SELECT passwordhash FROM users WHERE email = ?
2) Verify the entered password with the found hash
$isPasswordCorrect = password_verify($password, $existingHashFromDb);
It is the salt who makes the search impossible, it must be extracted from the stored hash before you can verify the password. Such a query would have to read each hash, extract its salt and do the hashing. Because the hash function is very slow (on purpose) the query would need ways too long to execute.

I think you mean how to select a hashed and salted password from a database then verify it with a plaintext password? If so, here is how with bcrypt.
Keep in mind, this requires PHP 5 >= 5.5.0.
Also, I recommend scrypt over bcrypt, but you have to install scrypt manually.
SQL stuff
CREATE DATABASE `example`;
USE `example`;
CREATE TABLE `users` (
`id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(16),
`password` VARCHAR(255)
);
Hash Class (classes/Hash.class.php)
<?php
class Hash
{
public static function make($string)
{
$options = array('cost' => 11);
return password_hash($string, PASSWORD_BCRYPT, $options)
}
public static function check($password, $hash)
{
return password_verify($password, $hash);
}
}
Database Class (classes/DB.class.php)
<?php
class DB
{
private $dbhost = '127.0.0.1';
private $dbname = 'example';
private $dbuser = 'root';
private $dbpass = 'pass';
public function Connect()
{
return new PDO('mysql:host=' . $this->dbhost . ';dbname=' . $this->dbname, $this->dbuser, $this->pass);
}
}
User Class (classes/User.class.php)
<?php
require_once('DB.class.php');
require_once('Hash.class.php');
class User
{
private $db;
public function __construct()
{
$this->db = new DB();
$this->db = $this->db->Connect();
}
public function find($username)
{
$st = $this->db->prepare('SELECT * FROM `users` WHERE `username` = :username LIMIT 1');
$st->bindParam(':username', $username, PDO::PARAM_STR);
$st->execute();
if($st->rowCount())
{
return $st->fetch(PDO::FETCH_ASSOC);
}
return false;
}
public function create($username, $password)
{
$password = Hash::make($password);
$st = $this->db->prepare('INSERT INTO `users` (`username`, `password`) VALUES (:username, :password)');
$st->bindParam(':username', $username, PDO::PARAM_STR);
$st->bindParam(':password', $password, PDO::PARAM_STR);
$st->execute();
}
public function verify($username, $password)
{
$user = $this->find($username);
if($user)
{
if(Hash::check($password, $user['password']))
{
$_SESSION['isLoggedIn'] = true;
return true;
}
}
return false;
}
public function isLoggedIn()
{
if(isset($_SESSION['isLoggedIn']))
{
return true;
}
return false;
}
}
Registration (register.php)
<?php
require_once('classes/User.class.php');
$user = new User();
if($user->isLoggedIn())
{
header('Location: index.php');
die();
}
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
$username = $_POST['username'];
$password = $_POST['password'];
// Check if username and password exist
if(!isset($username) || !isset($password))
{
die('Username and password required');
}
// Check if values are not empty
if(empty($username) || empty($password))
{
die('Blank fields not allowed');
}
// Check if username length is in between 4 and 16
if(strlen($username) < 4 && strlen($username) > 16)
{
die('Username must be in between 4 and 16 characters');
}
// Check if username is alphanumeric
if(!ctype_alnum($username))
{
die('Username must be alphanumeric');
}
// Check password length
if(strlen($password) < 8)
{
die('Passwords should be at least 8 characters long');
}
// Check if username exists
$exists = $user->find($username);
if($exists)
{
die('Username already in use');
}
// Create account
$user->create($username, $password);
header('Location: login.php');
die();
}
?>
// HTML goes here
Login (login.php)
<?php
require_once('classes/User.class.php');
$user = new User();
if($user->isLoggedIn())
{
header('Location: index.php');
die();
}
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
$username = $_POST['username'];
$password = $_POST['password'];
// Check if username and password exist
if(!isset($username) || !isset($password))
{
die('Username and password required');
}
// Check if values are not empty
if(empty($username) || empty($password))
{
die('Blank fields not allowed');
}
// Check if username length is in between 4 and 16
if(strlen($username) < 4 && strlen($username) > 16)
{
die('Username must be in between 4 and 16 characters');
}
// Check if username is alphanumeric
if(!ctype_alnum($username))
{
die('Username must be alphanumeric');
}
// Check password length
if(strlen($password) < 8)
{
die('Passwords should be at least 8 characters long');
}
// Try to login
$verified = $user->verify($username, $password);
if($verified)
{
header('Location: index.php');
die();
} else {
die('Invalid username/password');
}
}
?>
// HTML goes here
Logout (logout.php)
<?php
require_once('classes/User.class.php');
$user = new User();
if($user->isLoggedIn())
{
unset($_SESSION['isLoggedIn']);
}
header('Location: login.php');
die();
Index (index.php)
<?php
require_once('classes/User.class.php');
if(!$user->isLoggedIn())
{
header('Location: login.php');
die();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Welcome</title>
</head>
<body>
<h1>Menu</h1>
<ul>
<li>Logout?</li>
</ul>
</body>
</html>

How, during a login, do I check against the hashed password column only, and only table's column, if the passwords match, having only 'blabla' as data?
You can't. Password storage is designed to make many operations impossible. If you want to find a match for a password without using the user name or some other key, you will need to call password_verify on every password until you get a match. By design, this will be very slow.
Being passwords don't need to be in unique, you may have one password that matches many entries.
My guess is that this is a bad idea and not what you want.

Related

Hashing password in register and account settings

I am get login errors when i test my script by logining under my own account. Do you think hashing passwords twice a bad practice?
I have hashed the users password twice in my website. Once, when they register and once, when they update their password in account update. Also i am using bcrypt method and cost of bcrypting is 10 on both hashings and i am on localhost server.
///// this is the code in register.php page
<?php
if(isset($_POST['registeruser'])) {
session_start();
$FName = $_POST['regfname'];
$LName = $_POST['reglname'];
$Email = mysqli_real_escape_string($conn, $_POST['regemail']);
$origignalpassword = preg_replace('#[^a-z0-9_]#i', '',
$_POST['regpassword']);
$Passwordw = $_POST['confirmedpassword'];
$infosql = "SELECT * FROM users WHERE useremail = '".$Email."'";
$result = mysqli_query($conn,$infosql);
if(mysqli_num_rows($result)>=1)
{
echo "Email already taken.";
}
else if(mysqli_num_rows($result) !=1 && $Passwordw ==
$origignalpassword) {
$Passwordhash = password_hash($Passwordw,
PASSWORD_BCRYPT, array('cost' => 10));
$sql = $conn->query("INSERT INTO users(firstname,
lastname, useremail, Passwordcell) Values('{$FName}',
'{$LName}','{$Email}','{$Psswordhash}')");
header('Location: login.php');
} else {
echo 'Please check your password:' . '<br>';
}
}
?>
//// Below code is the code in my update.php page
<?php session_start();
if(isset($_SESSION['user_id'])) {
} else {
header('Location: login.php');
}
$user = $_SESSION['userid'];
$myquery = "SELECT * FROM users WHERE `userid`='$user'";
$result = mysqli_query($conn, $myquery);
$row = mysqli_fetch_array($result, MYSQLI_BOTH);
$_SESSION['upd_fnames'] = $row['firstname'];
$_SESSION['upd_lnames'] = $row['Lastname'];
$_SESSION['upd_emails'] = $row['useremail'];
$_SESSION['upd_passwords'] = $row['Passwordcell'];
$_SESSION['upd_phone'] = $row['phonenum'];
$_SESSION['upd_bio'] = $row['biography'];
?>
<?php
if (isset($_POST['updateme'])) {
$updfname = $_POST['upd_fnames'];
$updlname = $_POST['upd_lnames'];
$updemail = $_POST['upd_emails'];
$updphone = $_POST['upd_phone'];
$upd_pswd = $_POST['upd_passwords'];
$biography = $_POST['update_biography'];
$Pswod = password_hash($upd_pswd, PASSWORD_BCRYPT,
array('cost' => 10));
$sql_input = $mysqli->query("UPDATE users SET firstname = '{$updfname}', Lastname = '{$updlname}', Phonenum = '{$updphone}', useremail = '{$updemail}', Passwordcell = '{$Pswod}', biography = '{$biography}' WHERE userid=$user");
header('Location: Account.php');
}
else
{
}
?>
Your problem could be just a typo, in your registration script, instead of $Passwordhash you wrote:
"INSERT INTO users(..., Passwordcell) Values(...,'{$Psswordhash}')"
Nevertheless there are other problems with your code, and since you asked for advise, i would like to share my thoughts.
Probably the biggest problem is, that your code is vulnerable to SQL-injection. Switch to prepared statements as soon as you can, writing code will become even easier than building the query as you did, and both MYSQLI and PDO are supporting it. This answer could give you a start.
Passwords should not be sanitized. Remove the line $origignalpassword = preg_replace('#[^a-z0-9_]#i', '', $_POST['regpassword']), and just pass the input directly to the hash function password_hash($_POST['regpassword'], PASSWORD_DEFAULT). The password_hash() function works with any type of input.
It is a good habit to place an exit after each redirection, otherwise the script will continue executing. header('Location: login.php', true, 303); exit;
Do you really have reason to put the user info into the session? Instead of $_SESSION['upd_fnames'] = $row['firstname']; i would fetch the information on demand from the database. With fetching it from the database you can be sure that the information is actually set (is not null) and is up to date, you can avoid a state and you get a bit more REST full.
Then last but not least i would recommend to follow some style rules, like starting variable names always with a small letter. You can avoid some silly typos and it makes your code more readable.

Email validation MySQL and PHP

I want to check if email already exists in the DB, starting at this and need some help. Here is the code:
<?php
require_once('includes/connects.php');
$username = $_POST['username'];
$nome = $_POST['nome'];
$password = sha1($_POST['password']);
$email = $_POST['email'];
$checkmail = mysql_query("SELECT email FROM utilizadores WHERE email = '$email'");
$result = $conn->query($checkmail);
if($result->num_rows == 0){
$querygo = "INSERT INTO utilizadores (username, nome, password, email) VALUES ('$username', '$nome', '$password', '$email'";
$result = $conn->query($querygo);
header('Location: index.php');
} else {
echo 'DONT WORK';
}
This is the error:
Fatal error: Call to a member function query() on a non-object in
C:\Sites\deca_13L4\deca_13L4_23\MiniProjeto\bussaco\registo.php on
line 12 Warning: Unknown: 1 result set(s) not freed. Use
mysql_free_result to free result sets which were requested using
mysql_query() in Unknown on line 0
Looking at your code, unsure what value this has in the mix of everything since that is exactly where the error occurs:
$result = $conn->query($checkmail);
So I would change your code to be like this:
<?php
require_once('includes/connects.php');
$username = $_POST['username'];
$nome = $_POST['nome'];
$password = sha1($_POST['password']);
$email = $_POST['email'];
$checkmail = mysql_query("SELECT email FROM utilizadores WHERE email = '$email'");
if(!$checkmail){
$querygo = "INSERT INTO utilizadores (username, nome, password, email) VALUES ('$username', '$nome', '$password', '$email'";
$result = $conn->query($querygo);
header('Location: index.php');
} else {
echo 'DONT WORK';
}
But that said, it’s unclear to me where the actual needed mysql_connect comes from. Is that in require_once('includes/connects.php');? I hope so. Otherwise mysql_query won’t work no matter what. To execute a query you need a connection.
just make your code as simple as you can. So here:
<?php
require_once('includes/connects.php');
$username = $_POST['username'];
$nome = $_POST['nome'];
$password = sha1($_POST['password']);
$email = $_POST['email'];
$checkmail = mysql_query("SELECT email FROM utilizadores WHERE email = '$email'");
$num_rows = mysql_num_rows($checkmail);
if($num_rows){
$querygo = "INSERT INTO utilizadores (username, nome, password, email) VALUES ('$username', '$nome', '$password', '$email'";
$result = mysql_query($querygo);
header('Location: index.php');
} else {
echo 'DONT WORK';
}

How to migrate mysqli to pdo

Hi I was wondering how I would migrate a mysqli php file to use PDO. Would anyone be able to take a look at my code and see if I'm on the right track?
This is my original (mysqli) code:
<?php
// connecting to database
$conn = new mysqli('xxxxxx', 'xxxxxx', 'password', 'xxxxxx');
$match_email = 'email';
$match_passhash = 'passhash';
if (isset($_POST['email'])) {
$clean_email = mysqli_real_escape_string($conn, $_POST['email']);
$match_email = $clean_email;
}
if (isset($_POST['passhash'])) {
$clean_passhash = mysqli_real_escape_string($conn, $_POST['passhash']);
$match_passhash = sha1($clean_passhash);
}
$userquery = "SELECT email, passhash, userlevel, confirmed, blocked FROM useraccounts
WHERE email = '$match_email' AND passhash = '$match_passhash'
AND userlevel='user' AND confirmed='true' AND blocked='false';";
$userresult = $conn->query($userquery);
if ($userresult->num_rows == 1) {
$_SESSION['authorisation'] = 'knownuser';
header("Location: userhome.php");
exit;
} else {
$_SESSION['authorisation'] = 'unknownuser';
header("Location: userlogin.php");
exit;
}
?>
And this is my attempt to migrate it to PDO:
<?php
// connecting to database
$dbh = new PDO("mysql:host=xxxxxx; dbname=xxxxxx", "xxxxxx", "password");
$match_email = 'email';
$match_passhash = 'passhash';
if (isset($_POST['email'])) {
$clean_email = mysqli_real_escape_string($conn, $_POST['email']);
$match_email = $clean_email;
}
if (isset($_POST['passhash'])) {
$clean_passhash = mysqli_real_escape_string($conn, $_POST['passhash']);
$match_passhash = sha1($clean_passhash);
}
$userquery = "SELECT email, passhash, userlevel, confirmed, blocked FROM useraccounts
WHERE email = ':match_email' AND passhash = ':match_passhash' AND
userlevel='user' AND confirmed='true' AND blocked='false';";
$stmt = $dbh->prepare($query);
$stmt->bindParam(":match_email", $match_email);
$stmt->bindParam(":match_passhash", $match_passhash);
$stmt->execute();
$userresult = $conn->query($userquery);
if ($userresult->num_rows == 1) {
$_SESSION['authorisation'] = 'knownuser';
header("Location: userhome.php");
exit;
} else {
$_SESSION['authorisation'] = 'unknownuser';
header("Location: userlogin.php");
exit;
}
?>
I'm also not sure how to count the number of rows returned in PDO.
If anyone would be able to help me out that wold be very great.
A million thanks in advance!
When using prepared statements and $stmt->bindValue() or $stmt->bindParam() you do not need to escape values with mysqli_real_escape_string(), PDO will do that for you.
Just remember to set a correct data type for the value. That is the third argument in the bind functions and it is a string by default so your code here is fine. I would only use bindValue() instead of bindParam() as you do not need references.
$stmt->execute() will run your prepared statement as a query. The other $conn->query() does not work with prepared statements. It is for raw queries, like you used to have with MySQLi.
When $stmt->execute() runs your response is saved in the $stmt object. For row count use $stmt->rowCount().

Zend Authentication and Login - accepts foo and foo1! both for a saved foo password

I'm trying to script an authentication and login script and am quiet there.. I have a password 'password' saved in my database... it works with the password 'password' however should not work with 'password1!' or prefix or suffix two characters more.... IT shouldn't accept it... but if I enter something different (different string, or a few more characters), it gives me a proper error...
That is the only row in my database table... there are no duplicate entries..
Following is the code
public function validate_login($valuethrown) {
$username = $valuethrown['username'];
$email = $valuethrown['email'];
$password = $valuethrown['password'];
echo "Values thrown to the model are : $username, $email, $password";
$authAdapter = $this->getAuthAdaptor();
$authAdapter->setIdentity($email)
->setCredential(md5($password));
$auth = Zend_Auth::getInstance();
$result = $auth->authenticate($authAdapter);
if ($result->isValid()) {
$data = $authAdapter->getResultRowObject(null,'password');
$auth->getStorage()->write($data);
$this->_redirect('home');
} else {
echo "</br>Wrong Password</br>";
}
//check login details
}
public function getAuthAdaptor() {
$authAdapter = new Zend_Auth_Adapter_DbTable(Zend_Db_Table::getDefaultAdapter());
$authAdapter->setTableName('users')
->setIdentityColumn('email')
->setCredentialColumn('password');
return $authAdapter;
}
HELP!!!!

Zend_Auth - Be Able To Login With Both Email And Username

So I am using Zend_Auth to authenticate users of my website. Currently they are only able to log in with their email as login but I would like to enable them to log in also with their username.
Here is some code:
// prepare adapter for Zend_Auth
$adapter = new Zend_Auth_Adapter_DbTable($this->_getDb());
$adapter->setTableName('users');
$adapter->setIdentityColumn('email');
$adapter->setCredentialColumn('password_hash');
$adapter->setCredentialTreatment('CONCAT(SUBSTRING(password_hash, 1, 40), SHA1(CONCAT(SUBSTRING(password_hash, 1, 40), ?)))');
$adapter->setIdentity($request->getParam('email'));
$adapter->setCredential($request->getParam('password'));
Notice the line:
$adapter->setIdentityColumn('email');
How can I add also username there (column in the database called username, too)?
UPDATE:
This is how I solved this:
$login = $request->getParam('email');
$validator = new Zend_Validate_EmailAddress();
if (false === $validator->isValid($login)) {
$u = $this->_getTable('Users')->getSingleWithUsername($login);
if (null === $u) {
throw new Exception ('Invalid login and/or password');
}
$login = $u->email;
}
// prepare adapter for Zend_Auth
$adapter = new Zend_Auth_Adapter_DbTable($this->_getDb());
$adapter->setTableName('users');
$adapter->setIdentityColumn('email');
$adapter->setCredentialColumn('password_hash');
$adapter->setCredentialTreatment('CONCAT(SUBSTRING(password_hash, 1, 40), SHA1(CONCAT(SUBSTRING(password_hash, 1, 40), ?)))');
$adapter->setIdentity($login);
$adapter->setCredential($request->getParam('password'));
I deal with the same thing, and I handle it before Zend_Auth. I use a single user sign-in field and first check whether it's an email address -- if so, it's converted to the appropriate username. Then, let Zend_Auth do its thing.
This works well for me, although you'll need to kinda switch it around, since you're going the other way.
i. Add a filter to your user sign-in field, like this:
$user_field->addFilter('EmailToUsername');
ii. The filter:
<?php
/**
* Converts an email address to the username.
*/
class Prontiso_Filter_EmailToUsername implements Zend_Filter_Interface
{
public function filter( $value )
{
if ( Zend_Validate::is($value, 'EmailAddress') ) {
$user_table = new Users();
$user = $user_table->findByEmail($value);
if ( $user ) {
return $user->username;
}
}
/**
* Nothing happened, so don't filter.
*/
return $value;
}
}
As for just changing a Zend_Auth setting instead, Zend_Auth doesn't like either/or identity columns, so you'd have to write your own auth adapter.
My own solution:
$adapter->setIdentityColumn(stripos($indentity, '#') ? 'email' : 'username');
Fast and simple!