Contents
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 “ytwqwxpbx1oxbfvmpoaafckmat2zkdsjaxs…” which will be stored to the database.
RELATED: PHP Login Script with Session Tutorial – Step by Step Guide!
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.
You see, even if someone steal your database, they will never read your actual or cleartext password.
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.
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:
CREATE TABLE IF 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>
<link type="text/css" rel="stylesheet" href="css/style.css" />
</head>
<body>
<div id="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" . $_POST['email'];
$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 = new PasswordHash(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
-->
<form action="register.php" method="post">
<div id="formHeader">Registration Form</div>
<div id="formBody">
<div class="formField">
<input type="email" name="email" required placeholder="Email" />
</div>
<div class="formField">
<input type="password" name="password" required placeholder="Password" />
</div>
<div>
<input type="submit" value="Register" class="customButton" />
</div>
<div id='userNotes'>
Already have an account? <a href='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>
<link type="text/css" rel="stylesheet" href="css/style.css" />
</head>
<body>
<div id="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 = new PasswordHash(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
-->
<form action="login.php" method="post">
<div id="formHeader">Website Login</div>
<div id="formBody">
<div class="formField">
<input type="email" name="email" required placeholder="Email" />
</div>
<div class="formField">
<input type="password" name="password" required placeholder="Password" />
</div>
<div>
<input type="submit" value="Login" class="customButton" />
</div>
</div>
<div id='userNotes'>
New here? <a href='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:thin solid #000;
width:300px;
margin:7em auto 0 auto;
}
#formHeader{
border-bottom:thin solid gray;
padding:10px;
background:#f3f3f3;
}
#loginForm{
}
.customButton {
padding:5px;
width:100%;
-moz-box-shadow:inset 0px 1px 0px 0px #bbdaf7;
-webkit-box-shadow:inset 0px 1px 0px 0px #bbdaf7;
box-shadow:inset 0px 1px 0px 0px #bbdaf7;
background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #79bbff), color-stop(1, #378de5) );
background:-moz-linear-gradient( center top, #79bbff 5%, #378de5 100% );
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#79bbff', endColorstr='#378de5');
background-color:#79bbff;
-moz-border-radius:6px;
-webkit-border-radius:6px;
border-radius:6px;
border:1px solid #84bbf3;
display:inline-block;
color:#ffffff;
font-family:arial;
font-size:15px;
font-weight:bold;
text-decoration:none;
text-shadow:1px 1px 0px #528ecc;
cursor:pointer;
}
.customButton:hover {
background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #378de5), color-stop(1, #79bbff) );
background:-moz-linear-gradient( center top, #378de5 5%, #79bbff 100% );
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
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!
You can download all the code used in this tutorial for only $9.99 $5.55!
Thank you for learning from our post about: How To Salt, Hash and Store Passwords Securely?
RELATED: PHP Login Script with Session Tutorial – Step by Step Guide!
Hello
If I am not mistaken, your code runs on the server side, so you are sending the password over internet in clear….
Stephane.
Hi @Stephane, yes but the password sent by the server is hashed and not the actual password.
I find it is best to use some data from the user as the salt. that can be part of a string or it can be their login username or something. that way the salt is always random.
Hi @disqus_8QO3F20Vmo , yes, that’s another way to do it. We can concatenate a user data in the $salt variable above.
well the problem with concatenating onto a static string is that it will be possible to locate that static string. a salt should only be 1 string and it doesn’t have to be complex. it can be simple.
Would you tell us how? Please show us a sample code, we’ll appreciate it! Many software like WordPress uses a ‘static string’ like what we used above, I’m sure they concatenate something on it too.
Dear readers, this article is from 03/2013 (!) and outdated. For secure password hashing please only use PHP 5.5’s native password hashing API, not phpass!
Hey guys @jonathanbernardi and @devmetal, thanks for the useful comments!
But do you think my current blog post above will still help those users in PHP version < 5.3? I think they currently have significant numbers, according to this http://wordpress.org/about/stats/ and this http://w3techs.com/technologies/details/pl-php/5/all . I'm going to update this post soon.
@ninjazhai Hmm, should we really support people who are still using 5.2, which is dead for 5 years (!) now? People with 5.2 are doing EVERYTHING wrong and are should not get any support from the community in my opinion.
I prefer to use the password hashing functions built into php.
http://us3.php.net/manual/en/ref.password.php
They are available on php 5.5+ and there is a library that will backport them to php 5.3.7+
https://github.com/ircmaxell/password_compat
It will also let you future proof your password hashing. Currently the best password hashing method is bcrypt. It is reasonable to assume that someday a better method will exist. If you use the password_hash function it will automatically start using the newer one as soon as it is available without having to change your code.
Also bcrypt (which both phpass and password_hash use) adds a salt to the password by itself so there is no need to add one yourself.
Double salting does not add any extra security. Using the same salt for every password likely even reduces security.
Is this hashed with MD5 + salt? I dont see in your code where you put the hash. i only saw your salt value
Always use a random salt per user registration instead of using a static one in your code.
Thanks for the tip @1fb9141a5eb3333aaddb662f1da47976! That will help for sure!
If you wouldn’t mind adding it, please put some validation on that email address like:
filter_var($_POST[’email’], FILTER_VALIDATE_EMAIL);
Using the bound parameters is good, but you want to be sure the data’s good too :) Input filtering ftw!
Sure, thanks for sharing @twitter-8854032! Using filter_var is a good technique! :D