I was just reading this article the other day about how 4chan hackers compromised a Christian singles dating site. They went on to use the emails / passwords gathered to launch an attack on these users Facebook accounts. On Facebook, they posted a lot of not safe for work pictures and wrote many profane status updates.
This got me thinking about a lot of projects where I had to correct and secure web applications that store plain text passwords. Perhaps I’m just paranoid, but it baffles me how many projects out there maintain this process.
Hopefully, this script can be useful to those who are looking to improve the security of their authentication system.
The Code
<?php
function generate_salt($email) {
// Create a SHA1 hash
$salt = sha1('~'.$email.'~'.microtime(TRUE).'~');
// Limit to random 10 characters in the hash
$salt = substr($salt, rand(0,30), 10);
return $salt;
}
function hash_password($password, $salt) {
return sha1('~'.$password.'~'.$salt.'~');
}
function valid_password($password, $hash, $salt) {
return hash_password($password, $salt) == $hash;
}
// CREATING A USER:
echo "*** CREATE USER ***\n";
// -> This data would be gathered from a signup form
$form_email = 'bill@test.com';
$form_pass = 'password';
// -> We would have to generate a password hash and salt for them
echo 'SALT = '.generate_salt($form_email)."\n";
echo 'HASH = '.hash_password($password, $salt)."\n";
echo "\n";
// CHECKING CREDENTIALS:
echo "*** LOG USER IN ***\n";
// -> This data would be from the login form
$form_email = 'jack@test.com';
$form_pass = 'secret';
// -> This data would be gathered from the user's database record
$db_hash = '99079df72d0cc065ebde85da906a544b5078cf7d';
$db_salt = 'a1ef1118b2';
if (valid_password($form_pass, $db_hash, $db_salt))
echo "LOGGED IN\n";
else
echo "NOT LOGGED IN\n";
Explanation
Instead of saving passwords in straight text, we will be generating a random salt string and use it to obscure the password being saved into the database.
The generate_salt() function takes the user’s email address, adds the current UNIX timestamp, and creates a 40 character SHA1 hash. To further obscure the salt, I randomly select at 10 character string within the hash to be the salt.
Once we have a salt string, we can call hash_password() to create a hash string that with the password and salt to be stored in the database. Each time a user tries to login, the password entered would have to be run against this function and compared against the value stored in the database.
For an example of checking if an entered password is valid, check out the valid_password() function.
Addendum:
Special thanks to Julius Beckmann. He provided code to further make the password hashing process more difficult to crack:
// Very complex hash function (GNU)
// Written by: http://juliusbeckmann.de/
// This function is using only sha1() and strrev() which are
// available on every PHP5 installation.
function hash_password($p, $s, $iter=5) {
// ALWAYS return a multiple hashed pass salt combination
$hash = sha1(sha1($p.$s).sha1(strrev($p).strrev($s)));
// Rehashing the hash will make cracking process much slower
for($i=0;$i< =$iter;++$i)
$hash = sha1(sha1($hash).sha1(strrev($hash))));
return $hash;
}
I also added code to generate more complex case-sensitive salt strings then the hexadecimal strings from before:
function generate_salt($length = 20){
// Purposely left off quotation marks so it doesn't mess with SQL
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.
'0123456789``-=~!@#$%^&*()_+,./<>?;:[]{}\|';
$str = '';
$max = strlen($chars) - 1;
for ($i=0; $i < $length; $i++)
$str .= $chars[rand(0, $max)];
return $str;
}
11 Comments to “How to salt and hash user passwords in PHP”
Post comment
What I do
Build highly scalable web applications in Ruby on Rails, PHP MVC, and Python / Django.
Apache, MySQL, Memcache, CRON jobs, and the magic sauce that make web apps tick.
Develop applications for iPhone and OS X in Objective-C / Cocoa for kicks and giggles.
View my portfolio »
Popular Posts
- How to submit a form with AJAX in jQuery 19 comment(s) | 27 view(s) per day
- A simple forum using Kohana PHP and ORM 2 comment(s) | 20 view(s) per day
- How to salt and hash user passwords in PHP 11 comment(s) | 17 view(s) per day
Recent Posts
Tags
.NET AJAX ASP.NET Bebo Blog C# CakePHP CMS CSS Database Dojo E-Commerce Facebook Form HTML IIS Image Gallery Javascript jQuery Kohana LAMP Lead Generation Lightbox Linux MS SQL MVC MySpace MySQL ORM Performance Photoshop PHP POST Prototype / Scriptaculous REST Ruby On Rails Scalability Security SEO Silverpop Social Networking User Authentication Wordpress XML-RPC Zend FrameworkArchives
- January 2010 (2)
- September 2009 (1)
- August 2009 (2)
- June 2009 (10)
- May 2009 (4)
Latest Blog Posts
About me
My name is George. I am a Software Engineer with about three years experience developing in open-source internet technology. I currently live and work in Los Angeles, California.
I am currently a Ruby on Rails developer. But the majority of my experience has been in PHP. I have also dabbled in C# / ASP.NET and Python / Django for various projects.
View my résumé or contact me today.
Get in touch!
You may either use my contact form, or send and email to me directly at george@georgetruong.com.
You could also try to reach me at 626-817-2633.











There is no reason to complicate salt that way (I mean, sha1 email, etc.). As you already store it in the database, don’t you?
Any random string would do the job as salt is used in order to force password to be longer so rainbow tables does not decipher it. (currently simple passwords of length 6-8 chars are deciphered easily).
However, I would recommend using a bit longer salt string. 32 bytes maybe? However it does not make much difference as everything will float your boat as long as that everything is not plain text or not salted md5.
Oh, and the easiest method of salting is: md5(md5($pass)); however, I don’t like this one
Yeah. You are right regarding complicating the salt. You can pretty much use anything as a salt. Anything is fine as long as you don’t store raw text or a straight password hash. You’ll be surprised how easily you can google a straight SHA1 hash like 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8 and find the corresponding password.
I think the reason I did it this way was because we didn’t want to make the salt strings too long for storage reasons. It will be added to the password anyways to create a hash. Plus, a username and timestamp hash algorithm is pretty common so I decided to mix things up by randomly selecting a 10 character substring. I pretty much create a new variation of salting on each project.
Besides not needing to complicate the salt the fact that you are using a different salt for every user is very good. Regenerating a rainbow table for a complete userbase could make sence, regenerating rainbow tables for each user will take as long as a brute-force attack.
With different salt values for each user, you can have tons of people using the password ‘password’ and not be able to tell from database records.
I agree, any random string suffices for a salt.
Nice article with one big mistake.
You are completely right with using salts and unnormal chars in your password.
BUT, using non standard chars and salt does not make the hashing process complex enough.
Imagine you want to crack (not deciffer, because its a hash) a hash because you got access to the database and have hash and salt now. You would use a simple programm that will try everything from a to 99999999. Every step will include doing hash_password($password, $salt) wich will result in 1 (!) sha1().
That is NOT complex enough for secure hashes.
I rewrote your hash_password():
// Very complex hash function (GNU) // Written by: juliusbeckmann.de // This function is using only sha1() and strrev() which are // available on every PHP5 installation. function hash_password($p, $s, $iter=5) { // ALWAYS return a multiple hashed pass salt combination $hash = sha1(sha1($p.$s).sha1(strrev($p).strrev($s))); // Rehashing the hash will make cracking process much slower for($i=0;$i<=$iter;++$i) $hash = sha1(sha1($hash).sha1(strrev($hash)))); return $hash; }Your function will always return a single sha1() hash. My function will return at least a hash with 3xsha1() and 2x strrev(). A cracker programm will take at least 3-4 times longer to crack such a hash. If $i is set to 5 it will do sha1() and strrev() 5 times more which means a 15 times slower cracking progress.
Hope you could follow my explanations.
Regards, Julius Beckmann
Thanks for providing a more complex and secure rewrite of the hash password function. Although SHA1 is not fool proof (it can be cracked as well), rehashing several times would definitely increase the time it takes.
[...] How to salt and hash user passwords in PHP [...]
What’s wrong with MD5 & Secret Key Combination? MD5 is near impossible to crack and even though there’s evidence to suggest overlapping of hashes, it’s a one in a million chance of it happening.
Try:
md5( md5($password) . md5($secretKey) )
Then you just need to compare the hash there with the hash in the database. Storing it in session or cookie etc. can just be stored as;
md5($password)
and then when you come to comparing it just add the md5′d secret key and hash all that.
“MD5 is near impossible to crack”
don’t think so… MD5 has been cracked before (goolge a bit you’ll find it), i would not call it “near impossible”