I have been reading up on password hashing, but all the forums I read are full of posts from people debating theory behind it that I don't really understand.
I have an old (and presumably extremely weak) password script that reads like this: $hash = sha1($pass1);
function createSalt()
{
$string = md5(uniqid(rand(), true));
return substr($string, 0, 3);
}
$salt = createSalt();
$hash = sha1($salt . $hash);
If I understand correctly, the longer the salt, the larger the table the hacker has to generate in order to break the hash. Please correct me if I am wrong.
I am looking to write a new script that is more secure, and I am thinking that something like this would be okay:
function createSalt()
{
$string = hash('sha256', uniqid(rand(), true));
return $string;
}
$hash = hash('sha256', $password);
$salt = createSalt();
$secret_server_hash = 'ac1d81c5f99fdfc6758f21010be4c673878079fdc8f144394030687374f185ad';
$salt2 = hash('sha256', $salt);
$hash = $salt2 . $hash . $secret_server_hash;
$hash = hash('sha512', $hash );
Is this more secure? Does this have a noticeable amount of overhead?
Most importantly, is there some better way to make sure that the passwords in my database cannot be (realistically) recovered by cryptanalysis, thus ensuring that the only way security will be compromised is through my own error in coding?
Upon reading all of your answers and further reasearching, I have decided to go ahead and implement the bcrypt method of protecting my passwords. That being said, for curiosity's sake, if I were to take my above code and put a loop on it for say, 100,000 iterations, would that accomplish something similar to the strength/security of bcrypt?
password_hash() creates a new password hash using a strong one-way hashing algorithm. The following algorithms are currently supported: PASSWORD_DEFAULT - Use the bcrypt algorithm (default as of PHP 5.5. 0).
PHP provides a native password hashing API that safely handles both hashing and verifying passwords in a secure manner. Another option is the crypt() function, which supports several hashing algorithms.
PHP has a hash algorithm to encrypt the password. The most commonly used functions for password encrypting are md5(), crypt() and password_hash(). Assume we have the registration form data in the POST, which includes the username and password.
To the time of writing, SHA-256 is still the most secure hashing algorithm out there. It has never been reverse engineered and is used by many software organizations and institutions, including the U.S. government, to protect sensitive information.
Salts can only help you so far. If the hashing algorithm you use is so fast that there is little to no cost for generating rainbow tables, your security is still compromised.
A few pointers:
function strong_hash($input, $salt = null, $algo = 'sha512', $rounds = 20000) {
if($salt === null) {
$salt = crypto_random_bytes(16);
} else {
$salt = pack('H*', substr($salt, 0, 32));
}
$hash = hash($algo, $salt . $input);
for($i = 0; $i < $rounds; $i++) {
// $input is appended to $hash in order to create
// infinite input.
$hash = hash($algo, $hash . $input);
}
// Return salt and hash. To verify, simply
// passed stored hash as second parameter.
return bin2hex($salt) . $hash;
}
function crypto_random_bytes($count) {
static $randomState = null;
$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($randomState === null) {
$randomState = microtime();
if(function_exists('getmypid')) {
$randomState .= getmypid();
}
}
for($i = 0; $i < $count; $i += 16) {
$randomState = md5(microtime() . $randomState);
if (PHP_VERSION >= '5') {
$bytes .= md5($randomState, true);
} else {
$bytes .= pack('H*', md5($randomState));
}
}
$bytes = substr($bytes, 0, $count);
}
return $bytes;
}
Instead of deploying your own (inherently with flaws) hash/salt algorithm, why not use one that was developed by security professionals?
Use bcrypt. It's been developed exactly for this in mind. It slowness and multiple rounds ensures that an attacker must deploy massive funds and hardware to be able to crack your passwords. Add to that per-password salts (bcrypt REQUIRES salts) and you can be sure that an attack is virtually unfeasible without either ludicrous amount of funds or hardware.
The Portable PHP Hashing Framework in non-portable mode allows you to generate hashes using bcrypt easily.
You can also use crypt()
function to generate bcrypt hashes of input strings. If you go down that route, make sure you generate one salt per hash.
This class can automatically generate salts and verify existing hashes against an input.
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) {
return strtr(rtrim(base64_encode($input), '='), '+', '.');
}
}
You may use this code as such:
$bcrypt = new Bcrypt(15);
$hash = $bcrypt->hash('password');
$isGood = $bcrypt->verify('password', $hash);
If I understand correctly, the longer the salt, the larger the table the hacker has to generate in order to break the hash. Please correct me if I am wrong.
Yes, that's correct. Although if someone tries to break a hash of only one user, salt values are useless. Salts are useful for preventing (slowing down) attackers doing dictionary attack on all of your users hash values.
Let me explain that with an example. Suppose you have 3 users in your system and you don't use a salt value, so your database would like this:
user1: hash1
user2: hash2
user3: hash3
Now let's assume that an attacker achieves to get a copy of your database. He could now do a dictionary attack by doing:
h = hash(possible_password)
h == hash1?
h == hash2?
h == hash3?
And so, he could check if one of the 3 users has the password possible_password
by only calling the hash function one time.
No suppose you save the hash values which were combined with salt values in your database like this:
user1: hash1_salted, salt1
user2: hash2_salted, salt2
user3: hash3_salted, salt3
And again an attacker copies your database. But now in order to see if possible_password
is used by one of the 3 users he must do the following checks:
hash(possible_password + salt1) == hash1_salted?
hash(possible_password + salt2) == hash2_salted?
hash(possible_password + salt3) == hash3_salted?
As you see, in this case the attacker is slowed down by a factor of 3 (the number of users in your system), as he must hash 3 diffferent strings. That's the general idea behind salt values, you could read more on wikipedia.
But in your case, the salt is too big. What you want to prevent is 2 different user hashes to have the same salt value. So, for example a salt of 2 bits length won't probably be a good idea (for more than 4 users it will be sure that 2 have the same salt value). Anyway, a salt value of more than 48 bits will be enough.
Also, there is not really a point in hashing the salt here $salt2 = hash('sha256', $salt);
, this could slow things somehow, but in general adding more complexity in your system is considered bad when dealing with security.
Finally, it's never good to have specific values in your code when dealing with security, like $secret_server_hash
, such constant values should always be avoided.
It's better that you use SHA-2, instead of MD5, because in the recent years some security vulnerabilities have been found in MD5 (although they are not yet really practical).
So I would do something like this:
function createSalt()
{
$string = hash('sha256', uniqid(rand(), true));
return susbstr($string, 0, 8); // 8 characters is more than enough
}
$salt = createSalt();
$hash = hash('sha256', $hash . $password );
And then save the $hash
at your database.
Anyway, as some users already pointed out. Instead of creating your own security functions (which is a good way for learning about security) you should better use well-known libraries which are tested by a greater amount of people and hence probably more secure. In your case you should have a look at crypt
which does what you need.
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