Tuesday, May 29, 2012

How do you use bcrypt for hashing passwords in PHP?


Every now and then I hear the advice "Use bcrypt for storing passwords in PHP, bcrypt rulllez!!!11"



OK, but what is this bcrypt? PHP doesn't offer any such functions, wikipedia babbles about a file-encryption utility and Googling just reveals a few implementations of blowfish in different languages. OK, blowfish is also available in PHP via mcrypt, but how does that help with storing passwords? Blowfish is a general purpose cypher, it works two ways. If it could be encrypted, it can be decrypted. Passwords need a one-way hashing function.



Could anyone explain?


Source: Tips4all

5 comments:

  1. 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.

    ReplyDelete
  2. You 'll get a lot of infos 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 chose how slow it is.

    ReplyDelete
  3. You can create a one-way hash with bcrypt using PHP's crypt() function and passing in an appropriate Blowfish salt. I don't see any compelling reason to use it over SHA though. 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

    ReplyDelete
  4. 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.

    ReplyDelete
  5. You Don't

    Use hash("sha512", $str); instead. You could also use another hashing algorithm (MD5, SHA1, SHA256) if you like.

    See this related question. The argument that bcrypt is better because it is slower is not smart. You can generate a rainbow table to crack all <6 character passwords in a few days and then crack passwords in a few minutes. The solution to this is to enforce strong passwords on the user end, not change how you generate a hash.

    Also, always use a salt.

    ::Edit::

    I've thought about deleting this but really it makes me chuckle whenever I see it so I'm okay with leaving it up. I still believe that insecure passwords will be insecure no matter if it takes 0.0000005 seconds or 5 seconds to generate a hash for it. Specifically when there are some interesting problems with implementation at the software level.

    ReplyDelete