Hello EZ Web Developers,
I'd read this in a lot of places that the best way of storing passwords in databases would be hashing(or encrypting, a little confused about which term refers to the irreversible algo) the password using bcrypt algorithm and I come across this class that some guy made, for performing bcrypt algorithm on passwords.
Here's the class :
<?php
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)
{
// The following is code from the PHP Password Hashing Framework
$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$output = '';
$i = 0;
do
{
$c1 = ord($input[$i++]);
$output .= $itoa64[$c1 >> 2];
$c1 = ($c1 & 0x03) << 4;
if ($i >= 16)
{
$output .= $itoa64[$c1];
break;
}
$c2 = ord($input[$i++]);
$c1 |= $c2 >> 4;
$output .= $itoa64[$c1];
$c1 = ($c2 & 0x0f) << 2;
$c2 = ord($input[$i++]);
$c1 |= $c2 >> 6;
$output .= $itoa64[$c1];
$output .= $itoa64[$c2 & 0x3f];
} while (1);
return $output;
}
}
?>
[size=78%]
Now, as you may know, passwords that are hashed(or encrypted) using bcrypt algorithm may have different results, thats why, there's a verify() function in the class given above that check whether a given string and a bcrypt hashed string are equivalent or not.
For example, the verify() is used like this :
[/size]include("includes/file_with_bcrypt_class.php");
$bcrypt = new Bcrypt();
$hash = $bcrypt->hash("abcd"); // Returns the bcrypt equivalent of "abcd"
$verify_1 = $bcrypt->verify("abcd", $hash); // This will return true as the value of $hash is the bcrypt equivalent of "abcd"
$verify_2 = $bcrypt->verify("1234", $hash); // This will return false as the value of $hash is NOT the bcrypt equivalent of "1234"
[size=78%]
Now, to make a log in credentials verification script, I could either do this :
[/size]require("includes/mysql_pdo_connect.php");
$username = $_POST['username'];
$password = sha1($_POST['password']); // or maybe use sha256 or something else with/without a salt
$query = $pdo_connect->prepare("SELECT * FROM `users` WHERE `username` = ? AND `password` = ?");
$query->execute(array($username, $password));
if($query->rowCount() == 1) // Keep in mind, my database has the sha1 or sha256 or... equivalent of the password
echo "Verified and authorised";
[size=78%]
OR I could do this :
[/size]require("includes/mysql_pdo_connect.php");
require("includes/file_with_bcrypt_class.php");
$bcrypt = new Bcrypt();
$username = $_POST['username'];
$password = $_POST['password'];
$query = $pdo_connect->prepare("SELECT `password` FROM `users` WHERE `username` = ?");
$query->execute(array($username));
if($query->rowCount() != 0)
{
$row = $query->fetch(PDO::FETCH_ASSOC); // Keep in mind, my database table has a bcrypt equivalent of the password
if($bcrypt->verify($password, $row['password'])) // If the function verify() returns true, user is verified and authorised
{
echo "Verified and authorised";
}
}
[size=78%]
In the first code, my code is protected from SQLi and quite secure but, there's a problem. There are so many rainbow tables around the internet and password may be 'decrypted'
In the second code, my code is secure from SQLi too(I guess), but the problem is, the function verify() is not an inbuilt PHP function(that is verified and experimented by PHP security experts), so there may be security holes in that function.
So, which one do I use for the best protection?
Opinions and reasons please everyone!! The better opinions and solutions we get, the better we learn.[/size]