Every now and then I hear the advice “Use bcrypt for storing passwords in PHP, bcrypt rules”.
But what is bcrypt
? PHP doesn’t offer any such functions, Wikipedia babbles about a file-encryption utility and Web searches just reveal a few implementations of Blowfish in different languages. Now Blowfish is also available in PHP via mcrypt
, but how does that help with storing passwords? Blowfish is a general purpose cipher, it works two ways. If it could be encrypted, it can be decrypted. Passwords need a one-way hashing function.
Could anyone explain?
NOTE: This question was originally posted at StackOverflow.com by Vilx-
- Laurie asked 14 years ago
- last edited 12 years ago
You do
bcrypt
is an hashing algorithm which is scalable with hardware (via a configurable number of rounds). Its 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.
bcrypt
uses the Eksblowfish algorithm to hash passwords. While the encryption phase of Eksblowfish and Blowfish are exactly the same, the key schedule phase of Eksblowfish ensures that any subsequent state depends on both salt and key (user password), and no state can be precomputed without the knowledge of both. Because of this key difference, bcrypt
is a one-way hashing algorithm. You cannot retrieve the plain text password without already knowing the salt, rounds and key (password). [Source]
You can use crypt()
function to generate bcrypt hashes of input strings. 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) {
// 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;
}
}
You may use this code as such:
$bcrypt = new Bcrypt(15);
$hash = $bcrypt->hash('password');
$isGood = $bcrypt->verify('password', $hash);
Alternatively, you may also use the Portable PHP Hashing Framework.
NOTE: This answer was originally posted at StackOverflow.com by Andrew Moore
- Judith answered 14 years ago
- last edited 12 years ago
-
@andrewliu: Call the
$bcrypt->verify()
function with the plain text as first parameter and the hash stored in the database as second parameter.NOTE: This comment was originally posted at StackOverflow.com by Andrew Moore
-
@andrewliu:
$bcrypt->verify($passwordAsProvidedByTheUser, $hashInTheDB);
If it returnstrue
, it is valid.NOTE: This comment was originally posted at StackOverflow.com by Andrew Moore
-
@andrewliu make sure the field in your database is long enough to hold the password hash
varchar(60)
i made that mistake for a while lol, and to check if itstrue
usevar_dump($bcrypt->verify('password', $hash))
NOTE: This comment was originally posted at StackOverflow.com by Andy Lobel
-
@ircmaxell:
mt_rand()
is seeded exactly the same way in 5.3 (getmypid()
is available on all systems... And you should know that, you are a ZCE after all). Arguably, the benefits for a cryptographically secure salt here is extremely small; in hashing, the salt is not considered a secret. It is simply there to force the attacker to generate a different rainbow table per password (instead of using the same for all). Being able to guess the next salt gives no benefit to the attacker (it's in plain text in the hash).NOTE: This comment was originally posted at StackOverflow.com by Andrew Moore
-
@MichaelLang: Good thing
crypt()
is peer-reviewed and verified then. The code above calls PHP'scrypt()
, which calls the POSIXcrypt()
function. All the code above does more is generating a random salt (which doesn't have to be cryptographically secure, the salt isn't considered a secret) before callingcrypt()
. Maybe you should do a little research yourself before calling wolf.NOTE: This comment was originally posted at StackOverflow.com by Andrew Moore
- 40 more comments
- You must login to post comments
Everyone wants to make this more complicated than it is. The crypt() function does most of the work.
function blowfishCrypt($password,$cost)
{
$chars='./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$salt=sprintf('$2a$%02d$',$cost);
//Create a 22 character salt
for($i=0;$i<22;$i++) $salt.=$chars[rand(0,63)];
return crypt($password,$salt);
}
Example:
$hash=blowfishCrypt('password',10); //This creates the hash
$hash=blowfishCrypt('password',12); //This creates a more secure hash
if(crypt('password',$hash)==$hash){ /*ok*/ } //This checks a password
I know it should be obvious, but please don’t use ‘password’ as your password.
NOTE: This answer was originally posted at StackOverflow.com by Jon Hulka
- Diana answered 12 years ago
- last edited 12 years ago
- You must login to post comments
You can create a one-way hash with bcrypt using PHP’s crypt()
function and passing in an appropriate Blowfish salt. The most important of the whole equation is that A) the algorithm hasn’t been compromised and B) you properly salt each password. Don’t use an application-wide salt; that opens up your entire application to attack from a single set of Rainbow tables.
http://php.net/manual/en/function.crypt.php
NOTE: This answer was originally posted at StackOverflow.com by coreyward
- Catherine answered 14 years ago
- last edited 13 years ago
- You must login to post comments
You’ll get a lot of information here: http://chargen.matasano.com/chargen/2007/9/7/enough-with-the-rainbow-tables-what-you-need-to-know-about-s.html or http://www.openwall.com/phpass/
The goal is to hash the password with something slow so someone getting your password database will die trying to bruteforce it (a 10ms delay to check a password is nothing for you, a lot for someone trying to bruteforce it). Bcrypt is slow and can be used with a parameter to choose how slow it is.
NOTE: This answer was originally posted at StackOverflow.com by Arkh
- Chris answered 14 years ago
- last edited 13 years ago
-
@coreyward worth noting that doing that is more harmful than not blocking at all; that is easily considered a "denial of service" vector. Just start spamming bad logins on any known accounts and you can disrupt many users very, very easily. It's better to tarpit (delay) the attacker than outright deny access, especially if it's a paying customer.
NOTE: This comment was originally posted at StackOverflow.com by damianb
- You must login to post comments
Current thinking: hashes should be slowest available, not fastest possible. This suppresses rainbow table attacks.
Also related, but precautionary: An attacker should never have unlimited access to your login screen. To prevent that: Set up an IP tracking table that records every hit along with the URI. If more than 5 attempts to login come from the same IP in any five minute period, block with explanation. A secondary approach is to have a two-tiered password scheme, like banks do. Putting a lock-out for failures on the second pass boosts security.
Summary: slow down the attacker by using time-consuming hash functions. Also, block on too many accesses to your login, and add a second password tier.
NOTE: This answer was originally posted at StackOverflow.com by FYA
- Timothy answered 13 years ago
- last edited 13 years ago
-
Those wondering why this answer was downvoted so heavily (when it seems to agree with what the current direction is in the rest of the answers), check the edit history. It's pulled a complete 180 since it was posted.
NOTE: This comment was originally posted at StackOverflow.com by damianb
-
Half way through 2012 and this answer is still wonky, how does a slow hashing algorithm prevent rainbow table attacks? I thought a random byte range salt did? I always thought the speed of the hashing algorithm dictates how many iterations they can send against the hash they got form you in a specific amount of time. Also NEVER EVER BLOCK A USER ON FAILED LOGIN ATTEMPTS trust me your users will get fed up, often on some sites I need to login near 5 times sometimes more before I remember my password for it. Also second pass tier doesn't work, two step auth with mobile phone code could though.
NOTE: This comment was originally posted at StackOverflow.com by Sammaye
-
@Sammaye I would agree with this to a point. I setup a block on 5 failed login attempts, before raising it quickly to 7, then 10 now its sitting on 20. No normal user should have 20 failed login attempts but its low enough to easily stop brute force attacks
NOTE: This comment was originally posted at StackOverflow.com by Bruce Aldridge
-
@BruceAldridge I personally would think it would be better to make your script pause for a random time after say, 7 failed logins and show a captcha rather than block. Blocking is a very aggresive move to take.
NOTE: This comment was originally posted at StackOverflow.com by Sammaye
-
@Sammaye I agree permanent blocks are bad. I'm referring to a temporary block that increases with the number of failed attempts.
NOTE: This comment was originally posted at StackOverflow.com by Bruce Aldridge
- You must login to post comments
NOTE: This comment was originally posted at StackOverflow.com by eykanal
NOTE: This comment was originally posted at StackOverflow.com by Vilx-
NOTE: This comment was originally posted at StackOverflow.com by eykanal
NOTE: This comment was originally posted at StackOverflow.com by Vilx-
bcrypt
is a one-way hashing algorithm versus an encryption scheme in my answer. There is this whole misconception thatbcrypt
is just Blowfish when in fact it has a totally different key schedule which ensures that plain text cannot be recovered from the cipher text without knowing the initial state of the cipher (salt, rounds, key).NOTE: This comment was originally posted at StackOverflow.com by Andrew Moore