Home » Questions » How do you use bcrypt for hashing passwords in PHP?

How do you use bcrypt for hashing passwords in PHP?

Answered
0
0

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-

  • Alan
    This question has been addressed previously, and their suggestion of using a standard library is excellent. Security is a complicated matter, and by using a package designed by someone who knows what the hell they're doing you're only helping yourself.

    NOTE: This comment was originally posted at StackOverflow.com by eykanal

  • Laurie
    @eykanal - that page doesn't even mention bcrypt, much less explain what it is.

    NOTE: This comment was originally posted at StackOverflow.com by Vilx-

  • Alan
    That's true. However, I included that link based on the title of the question, which IS answered in that question. Regarding the actual details of bcrypt, you can check out this journal paper. However, realize that your question is very broad; you're asking for a summary explanation of an entire field of research (which, I readily admit, I'm not familiar with myself).

    NOTE: This comment was originally posted at StackOverflow.com by eykanal

  • Laurie
    @eykanal - I don't ask an explanation of how it works. I just want to know what it is. Because whatever I can dig up on the net under the keyword "bcrypt", can be in no way used for hashing passwords. Not directly anyway, and not in PHP. OK, by now I understand that it's really the "phpass" package which uses blowfish to encrypt your password with a key that is derived from your password (in essence encrypting the password with itself). But referencing it as "bcrypt" is severely misleading, and that is what I wanted to clarify in this question.

    NOTE: This comment was originally posted at StackOverflow.com by Vilx-

  • Judith
    @Vilx: I've added more information as to why bcrypt is a one-way hashing algorithm versus an encryption scheme in my answer. There is this whole misconception that bcrypt 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

  • You must to post comments
Good Answer
1
0

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
    @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

  • Judith
    @andrewliu: $bcrypt->verify($passwordAsProvidedByTheUser, $hashInTheDB); If it returns true, it is valid.

    NOTE: This comment was originally posted at StackOverflow.com by Andrew Moore

  • Bill
    @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 its true use var_dump($bcrypt->verify('password', $hash))

    NOTE: This comment was originally posted at StackOverflow.com by Andy Lobel

  • Judith
    @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

  • Judith
    @MichaelLang: Good thing crypt() is peer-reviewed and verified then. The code above calls PHP's crypt(), which calls the POSIX crypt() 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 calling crypt(). Maybe you should do a little research yourself before calling wolf.

    NOTE: This comment was originally posted at StackOverflow.com by Andrew Moore

0
1

มันยอดมากพระเจ้า

  • You must to post comments
1
1

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

  • You must to post comments
1
0

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

  • You must to post comments
1
0

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

  • Anita
    @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 to post comments
0
0

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

  • Anita
    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

  • Glenn
    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

  • Curtis
    @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

  • Glenn
    @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

  • Curtis
    @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 to post comments
Showing 7 results
Your Answer
Guest Author
Post as a guest by filling out the fields below or if you already have an account.
Name*
E-mail*
Website
CAPTCHA*
Enter the characters shown on the image.