¿Cómo se usa bcrypt para hashear contraseñas en PHP?


De vez en cuando escucho el consejo "Use bcrypt para almacenar contraseñas en PHP, reglas bcrypt".

Pero, ¿qué es bcrypt? PHP no ofrece tales funciones, Wikipedia balbucea sobre una utilidad de cifrado de archivos y las búsquedas en la Web solo revelan algunas implementaciones de Blowfish en diferentes idiomas. Ahora Blowfish también está disponible en PHP a través de mcrypt, pero ¿cómo ayuda eso a almacenar contraseñas? Blowfish es un cifrado de propósito general, funciona de dos maneras. Si pudiera ser encriptado, se puede descifrar. Las contraseñas necesitan una función de hash unidireccional.

¿Cuál es la explicación?

Author: Peter Mortensen, 2011-01-25

9 answers

bcrypt es un algoritmo de hash que es escalable con hardware (a través de un número configurable de rondas). Su lentitud y múltiples rondas aseguran que un atacante debe desplegar fondos masivos y hardware para poder descifrar sus contraseñas. Añadir a eso por contraseña sales (bcrypt REQUIERE sales) y puede estar seguro de que un ataque es prácticamente inviable sin una cantidad ridícula de fondos o hardware.

bcrypt utiliza el algoritmo Eksblowfish para hash contraseña. Mientras que la fase de cifrado de Eksblowfish y Blowfish son exactamente las mismas, la fase de programación de claves de Eksblowfish asegura que cualquier estado posterior depende tanto de salt como de key (contraseña de usuario), y ningún estado puede ser precalculado sin el conocimiento de ambos. Debido a esta diferencia clave, bcrypt es un algoritmo de hash unidireccional. No puede recuperar la contraseña de texto sin conocer ya la sal, las rondas y la clave (contraseña). [Fuente]

Cómo usar bcrypt:

Usando PHP >= 5.5-DEV

Las funciones de hash de contraseñas ahora se han construido directamente en PHP >= 5.5. Ahora puede usar password_hash() para crear un bcrypt hash de cualquier contraseña:

<?php
// Usage 1:
echo password_hash('rasmuslerdorf', PASSWORD_DEFAULT)."\n";
// $2y$10$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// For example:
// $2y$10$.vGA1O9wmRjrwAVXD98HNOgsNpDczlqm3Jq7KnEd1rVAGv3Fykk1a

// Usage 2:
$options = [
  'cost' => 11
];
echo password_hash('rasmuslerdorf', PASSWORD_BCRYPT, $options)."\n";
// $2y$11$6DP.V0nO7YI3iSki4qog6OQI5eiO6Jnjsqg7vdnb.JgGIsxniOn4C

Para verificar una contraseña proporcionada por el usuario contra un hash existente, puede usar password_verify() como tal:

<?php
// See the password_hash() example to see where this came from.
$hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';

if (password_verify('rasmuslerdorf', $hash)) {
    echo 'Password is valid!';
} else {
    echo 'Invalid password.';
}

Usando PHP >= 5.3.7, = 5.3.3)

Hay un biblioteca de compatibilidad en GitHub creado en base al código fuente de las funciones anteriores escritas originalmente en C, que proporciona la misma funcionalidad. Una vez instalada la biblioteca de compatibilidad, el uso es el mismo que el anterior (menos la notación de matriz abreviada si todavía está en la 5.3.x branch).

Usando PHP (OBSOLETO)

Puede usar la función crypt() para generar hashes bcrypt de cadenas de entrada. Esta clase puede automáticamente genere sales y verifique los hashes existentes contra una entrada. Si está utilizando una versión de PHP superior o igual a 5.3.7, es muy recomendable que utilice la función integrada o la biblioteca compat. Esta alternativa se ofrece solo con fines históricos.

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 is slow on Windows
      $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 (true);

    return $output;
  }
}

Puedes usar este código así:

$bcrypt = new Bcrypt(15);

$hash = $bcrypt->hash('password');
$isGood = $bcrypt->verify('password', $hash);

Alternativamente, también puede usar el Portable PHP Hashing Framework.

 988
Author: Andrew Moore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-19 19:10:00

Entonces, ¿quieres usar bcrypt? Impresionante! Sin embargo, al igual que otras áreas de la criptografía, no debería hacerlo usted mismo. Si necesita preocuparse por algo como administrar claves, o almacenar sales o generar números aleatorios, lo está haciendo mal.

La razón es simple: es tan trivialmente fácil arruinar bcrypt. De hecho, si nos fijamos en casi cada pieza de código en esta página, te darás cuenta de que está violando al menos uno de estos comunes problema.

Acéptalo, la criptografía es difícil.

Déjelo a los expertos. Dejo para la gente cuyo trabajo es mantener estas bibliotecas. Si necesitas tomar una decisión, lo estás haciendo mal.

En su lugar, simplemente use una biblioteca. Existen varios dependiendo de sus necesidades.

Bibliotecas

Aquí hay un desglose de algunas de las API más comunes.

API de PHP 5.5 - (Disponible para 5.3.7+)

A partir de PHP 5.5, una nueva API para hash se están introduciendo contraseñas. También hay una biblioteca de compatibilidad shim mantenida (por mí) para 5.3.7+. Esto tiene el beneficio de ser una implementación revisada por pares y simple de usar.

function register($username, $password) {
    $hash = password_hash($password, PASSWORD_BCRYPT);
    save($username, $hash);
}

function login($username, $password) {
    $hash = loadHashByUsername($username);
    if (password_verify($password, $hash)) {
        //login
    } else {
        // failure
    }
}

Realmente, está destinado a ser extremadamente simple.

Recursos:

Zend \ Crypt \ Password\Bcrypt (5.3.2+)

Esta es otra API que es similar a la de PHP 5.5, y tiene un propósito similar.

function register($username, $password) {
    $bcrypt = new Zend\Crypt\Password\Bcrypt();
    $hash = $bcrypt->create($password);
    save($user, $hash);
}

function login($username, $password) {
    $hash = loadHashByUsername($username);
    $bcrypt = new Zend\Crypt\Password\Bcrypt();
    if ($bcrypt->verify($password, $hash)) {
        //login
    } else {
        // failure
    }
}

Recursos:

PasswordLib

Este es un enfoque ligeramente diferente para el hashing de contraseñas. En lugar de simplemente soportar bcrypt, PasswordLib soporta un gran número de algoritmos hash. Es principalmente útil en contextos donde debe admitir la compatibilidad con sistemas heredados y dispares que pueden estar fuera de su control. Es compatible con un gran número de algoritmos de hash. Y es compatible con 5.3.2 +

function register($username, $password) {
    $lib = new PasswordLib\PasswordLib();
    $hash = $lib->createPasswordHash($password, '$2y$', array('cost' => 12));
    save($user, $hash);
}

function login($username, $password) {
    $hash = loadHashByUsername($username);
    $lib = new PasswordLib\PasswordLib();
    if ($lib->verifyPasswordHash($password, $hash)) {
        //login
    } else {
        // failure
    }
}

Referencias:

  • Código fuente / Documentación: GitHub

PHPASS

Esta es una capa que soporta bcrypt, pero también soporta un algoritmo bastante fuerte que es útil si no tienes acceso a PHP >= 5.3.2... En realidad es compatible con PHP 3.0+ (aunque no con bcrypt).

function register($username, $password) {
    $phpass = new PasswordHash(12, false);
    $hash = $phpass->HashPassword($password);
    save($user, $hash);
}

function login($username, $password) {
    $hash = loadHashByUsername($username);
    $phpass = new PasswordHash(12, false);
    if ($phpass->CheckPassword($password, $hash)) {
        //login
    } else {
        // failure
    }
}

Recursos

Nota: No utilice las alternativas de PHPASS que no están alojadas en openwall, ¡son proyectos diferentes!!!

Acerca de BCrypt

Si observa, cada una de estas bibliotecas devuelve una sola cadena. Eso es por cómo BCrypt funciona internamente. Y hay un MONTÓN de respuestas al respecto. Aquí hay una selección que he escrito, que no voy a copiar/pegar aquí, pero enlace a:

Terminar

Hay muchas opciones diferentes. El que elijas depende de ti. Sin embargo, ALTAMENTE recomiendo que use una de las bibliotecas anteriores para manejar esto por usted.

De nuevo, si estás usando crypt() directamente, probablemente estás haciendo algo mal. Si tu código está usando hash() (o md5() o sha1()) directamente, casi definitivamente estás haciendo algo Equivocada.

Simplemente use una biblioteca...

 277
Author: ircmaxell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-17 16:53:19

Obtendrá mucha información en Basta Con Las Tablas Rainbow: Lo Que Necesita Saber Sobre Los Esquemas De Contraseñas Seguras o Portable PHP password hashing framework.

El objetivo es hash la contraseña con algo lento, para que alguien que obtenga su base de datos de contraseñas muera tratando de forzarla brutalmente (un retraso de 10 ms para verificar una contraseña no es nada para usted, mucho para alguien tratando de forzarla brutalmente). Bcrypt es lento y se puede utilizar con un parámetro para elegir qué tan lento es.

 45
Author: Arkh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-23 18:12:43

Puede crear un hash unidireccional con bcrypt usando la función crypt() de PHP y pasando una sal apropiada de pez globo. Lo más importante de toda la ecuación es que A) el algoritmo no ha sido comprometido y B) salte correctamente cada contraseña. No use una sal para toda la aplicación; que abre toda su aplicación para atacar desde un solo conjunto de tablas Rainbow.

PHP-Crypt Function

 35
Author: coreyward,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-11 08:21:07

Edit: 2013.01.15 - Si su servidor lo soportará, use la solución de martinstoeckli en su lugar.


Todo el mundo quiere hacer esto más complicado de lo que es. La función crypt () hace la mayor parte del trabajo.

function blowfishCrypt($password,$cost)
{
    $chars='./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    $salt=sprintf('$2y$%02d$',$cost);
//For PHP < PHP 5.3.7 use this instead
//    $salt=sprintf('$2a$%02d$',$cost);
    //Create a 22 character salt -edit- 2013.01.15 - replaced rand with mt_rand
    mt_srand();
    for($i=0;$i<22;$i++) $salt.=$chars[mt_rand(0,63)];
    return crypt($password,$salt);
}

Ejemplo:

$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

Sé que debería ser obvio, pero por favor no use 'password' como su contraseña.

 33
Author: Jon Hulka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:59

La Versión 5.5 de PHP tiene soporte incorporado para BCrypt, las funciones password_hash() y password_verify(). En realidad, estos son solo envoltorios alrededor de la función crypt(), y facilitará su correcta utilización. Se encarga de la generación de una sal aleatoria segura y proporciona buenos valores predeterminados.

La forma más fácil de usar estas funciones será:

$hashToStoreInDb = password_hash($password, PASSWORD_BCRYPT);
$isPasswordCorrect = password_verify($password, $existingHashFromDb);

Este código hash la contraseña con BCrypt (algoritmo 2y), genera una sal aleatoria de la fuente aleatoria del sistema operativo, y utiliza el parámetro de costo predeterminado (por el momento esto es 10). La segunda línea comprueba si la contraseña introducida por el usuario coincide con un valor hash ya almacenado.

Si desea cambiar el parámetro de costo, puede hacerlo así, aumentando el parámetro de costo en 1, duplica el tiempo necesario para calcular el valor hash:

$hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 11));

En contraste con el parámetro "cost", es mejor omitir el parámetro "salt", porque la función ya hace todo lo posible para cree una sal criptográficamente segura.

Para PHP versión 5.3.7 y posteriores, existe un paquete de compatibilidad , del mismo autor que hizo la función password_hash(). Para versiones de PHP anteriores a la 5.3.7 no hay soporte para crypt() con 2y, el algoritmo unicode safe BCrypt. Uno podría reemplazarlo con 2a, que es la mejor alternativa para versiones anteriores de PHP.

 25
Author: martinstoeckli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-16 19:34:34

Una alternativa es usar scrypt, diseñado específicamente para ser superior a bcrypt por Colin Percival en su artículo. Hay una extensión PHP de scrypt en PECL. Idealmente este algoritmo se incluiría en PHP para que pudiera ser especificado para las funciones password_* (idealmente como "PASSWORD_SCRYPT"), pero eso no está ahí todavía.

 6
Author: Synchro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-19 14:17:36

Pensamiento actual: los hashes deben ser los más lentos disponibles, no los más rápidos posibles. Esto suprime los ataques rainbow table .

También relacionado, pero precaución: Un atacante nunca debe tener acceso ilimitado a su pantalla de inicio de sesión. Para evitar esto: Configure una tabla de seguimiento de direcciones IP que registre cada visita junto con el URI. Si más de 5 intentos de inicio de sesión provienen de la misma dirección IP en un período de cinco minutos, bloquee con explicación. Un enfoque secundario es tener un esquema de contraseñas de dos niveles, como hacen los bancos. Poner un bloqueo por fallas en el segundo pase aumenta la seguridad.

Resumen: ralentiza al atacante usando funciones hash que consumen mucho tiempo. Además, bloquee demasiados accesos a su inicio de sesión y agregue un segundo nivel de contraseña.

 6
Author: FYA,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-13 17:10:07

Para OAuth 2 contraseñas:

$bcrypt = new \Zend\Crypt\Password\Bcrypt;
$bcrypt->create("youpasswordhere", 10)
 3
Author: Shemeer M Ali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-23 18:16:20