What is password hashing?
It turns a string (of any length) to a fixed length "fingerprint" that cannot be reversed. For example, my password is "i1love2coding3", when hashed, it can be converted to a 60 character "ytwqwxpbx1oxbfvmpoaafckmat2zkdsjaxsmxwqrawxqbmifcojt3xevrgtp" which will be stored to the database.
Why do we have to hash passwords?
I think the main reason why we have to hash passwords is to prevent passwords from being stolen or compromised.
I know that some PHP frameworks or CMS already provide this functionality, but I believe that it is important for us to know how its implementation can be made.
![]() |
Saved password was salted and hashed. |
We are going to use a Portable PHP Password Hashing Framework called phpass (pronounced "pH pass") recommended by a lot of forums and is used by some famous Web applications like phpBB3, WordPress, Drupal, Vanilla, etc.
This post will focus and provide you a quick grasp and basic idea on how to salt, hash and store passwords in a MySQL database. This is essential to your PHP login script.
Let's Code
Our SQL table structure looks like this:CREATETABLEIF NOT EXISTS `users` (
`id`int(11) NOT NULL AUTO_INCREMENT,
`email`varchar(32) NOT NULL,
`password`char(60) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
libs/PasswordHash.php - our password framework file, yes, it is just this one file. You can download it here.
libs/DbConnect.php - configuration to be connected to database.
register.php - The user registration page, this is where we are going to save the user's password. On this example web app, we require these two fields only during registration.
<html>
<head>
<title>registration page - php salt and hash password - www.codeofaninja.com</title>
<linktype="text/css"rel="stylesheet"href="css/style.css" />
</head>
<body>
<divid="loginForm">
<?php
// save the username and password
if($_POST){
try{
// load database connection and password hasher library
require'libs/DbConnect.php';
require'libs/PasswordHash.php';
/*
* -prepare password to be saved
* -concatinate the salt and entered password
*/
$salt="ipDaloveyBuohgGTZwcodeRJ1avofZ7HbZjzJbanDS8gtoninjaYj48CW";
$password=$salt.$_POST['password'];
/*
* '8' - base-2 logarithm of the iteration count used for password stretching
* 'false' - do we require the hashes to be portable to older systems (less secure)?
*/
$hasher=newPasswordHash(8,false);
$password=$hasher->HashPassword($password);
// insert command
$query="INSERT INTO users SET email = ?, password = ?";
$stmt=$con->prepare($query);
$stmt->bindParam(1, $_POST['email']);
$stmt->bindParam(2, $password);
// execute the query
if($stmt->execute()){
echo"<div>Successful registration.</div>";
}else{
echo"<div>Unable to register. <a href='register.php'>Please try again.</a></div>";
}
}
//to handle error
catch(PDOException$exception){
echo"Error: ".$exception->getMessage();
}
}
// show the registration form
else{
?>
<!--
-where the user will enter his email and password
-required during registration
-we are using HTML5 'email' type, 'required' keyword for a some validation, and a 'placeholder' for better UI
-->
<formaction="register.php"method="post">
<divid="formHeader">Registration Form</div>
<divid="formBody">
<divclass="formField">
<inputtype="email"name="email"requiredplaceholder="Email" />
</div>
<divclass="formField">
<inputtype="password"name="password"requiredplaceholder="Password" />
</div>
<div>
<inputtype="submit"value="Register"class="customButton" />
</div>
<divid='userNotes'>
Already have an account? <ahref='login.php'>Login</a>
</div>
</div>
</form>
<?php
}
?>
</div>
</body>
</html>
login.php - the user login page, we are going to check if the users's password is valid or not .
<html>
<head>
<title>login page - php salt and hash password - www.codeofaninja.com</title>
<linktype="text/css"rel="stylesheet"href="css/style.css" />
</head>
<body>
<divid="loginForm">
<?php
// form is submitted, check if acess will be granted
if($_POST){
try{
// load database connection and password hasher library
require'libs/DbConnect.php';
require'libs/PasswordHash.php';
// prepare query
$query="select email, password from users where email = ? limit 0,1";
$stmt=$con->prepare( $query );
// this will represent the first question mark
$stmt->bindParam(1, $_POST['email']);
// execute our query
$stmt->execute();
// count the rows returned
$num=$stmt->rowCount();
if($num==1){
//store retrieved row to a 'row' variable
$row=$stmt->fetch(PDO::FETCH_ASSOC);
// hashed password saved in the database
$storedPassword=$row['password'];
// salt and entered password by the user
$salt="ipDaloveyBuohgGTZwcodeRJ1avofZ7HbZjzJbanDS8gtoninjaYj48CW";
$postedPassword=$_POST['password'];
$saltedPostedPassword=$salt.$postedPassword;
// instantiate PasswordHash to check if it is a valid password
$hasher=newPasswordHash(8,false);
$check=$hasher->CheckPassword($saltedPostedPassword, $storedPassword);
/*
* access granted, for the next steps,
* you may use my php login script with php sessions tutorial :)
*/
if($check){
echo"<div>Access granted.</div>";
}
// $check variable is false, access denied.
else{
echo"<div>Access denied. <a href='login.php'>Back.</a></div>";
}
}
// no rows returned, access denied
else{
echo"<div>Access denied. <a href='login.php'>Back.</a></div>";
}
}
//to handle error
catch(PDOException$exception){
echo"Error: ".$exception->getMessage();
}
}
// show the registration form
else{
?>
<!--
-where the user will enter his email and password
-required during login
-we are using HTML5 'email' type, 'required' keyword for a some validation, and a 'placeholder' for better UI
-->
<formaction="login.php"method="post">
<divid="formHeader">Website Login</div>
<divid="formBody">
<divclass="formField">
<inputtype="email"name="email"requiredplaceholder="Email" />
</div>
<divclass="formField">
<inputtype="password"name="password"requiredplaceholder="Password" />
</div>
<div>
<inputtype="submit"value="Login"class="customButton" />
</div>
</div>
<divid='userNotes'>
New here? <ahref='register.php'>Register for free</a>
</div>
</form>
<?php
}
?>
</div>
</body>
</html>
css/style.css - just for some styling.
body{
font:20px"Lucida Grande", Tahoma, Verdana, sans-serif;
color:#404040;
}
input[type=text],
input[type=password],
input[type=email]{
padding:10px;
width:100%;
}
#userNotes{
font-size:0.7em;
text-align:left;
padding:10px;
}
#actions{
padding:10px;
}
#infoMesssage{
padding:10px;
background-color:#BDE5F8;
color:black;
font-size:0.8em;
}
#successMessage{
padding:10px;
background-color:green;
color:white;
}
#failedMessage{
padding:10px;
background-color:red;
color:white;
font-size:15px;
}
#formBody{
padding:5px;
}
#loginForm{
text-align:center;
border:thinsolid#000;
width:300px;
margin:7emauto0auto;
}
#formHeader{
border-bottom:thinsolidgray;
padding:10px;
background:#f3f3f3;
}
#loginForm{
}
.customButton {
padding:5px;
width:100%;
-moz-box-shadow:inset0px1px0px0px#bbdaf7;
-webkit-box-shadow:inset0px1px0px0px#bbdaf7;
box-shadow:inset0px1px0px0px#bbdaf7;
background:-webkit-gradient( linear, lefttop, leftbottom, color-stop(0.05, #79bbff), color-stop(1, #378de5) );
background:-moz-linear-gradient( centertop, #79bbff5%, #378de5100% );
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#79bbff', endColorstr='#378de5');
background-color:#79bbff;
-moz-border-radius:6px;
-webkit-border-radius:6px;
border-radius:6px;
border:1pxsolid#84bbf3;
display:inline-block;
color:#ffffff;
font-family:arial;
font-size:15px;
font-weight:bold;
text-decoration:none;
text-shadow:1px1px0px#528ecc;
cursor:pointer;
}
.customButton:hover {
background:-webkit-gradient( linear, lefttop, leftbottom, color-stop(0.05, #378de5), color-stop(1, #79bbff) );
background:-moz-linear-gradient( centertop, #378de55%, #79bbff100% );
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#378de5', endColorstr='#79bbff');
background-color:#378de5;
}
.customButton:active {
position:relative;
top:1px;
}
/* This imageless css button was generated by CSSButtonGenerator.com */
Demo Screenshots
![]() |
Empty database. |
![]() |
Registration form. |
![]() |
Just a sample HTML5 validation. |
![]() |
After successful registration. |
![]() |
Our database will have the record. Notice the password field, it was hashed. |
![]() |
Our login page. |
![]() |
Just an example HTML5 validation during login. |
![]() |
Login with wrong credentials. |
![]() |
After login with correct username and password. |
Some references
- We should never store passwords as plain text.
- Add a long, unique random salt to each password you store so that brute force attacks will be a waste of time.
- If you want to have a deeper understanding and learn more techniques, I highly recommend reading the documentation, it's kinda long, but it's worth your time!
- Salted Password Hashing - Doing it Right
- How to store salt?
- Use bcrypt
Please note that password hashing is often wrongly referred to as "password encryption". Hashing is a more appropriate term since encryption is something that is supposed to be easily reversible. ~ phpass
If there's something you want to add, something wrong, or any questions, please let me know in the comments. Thanks a lot!