29 de abril de 2015

Login PHP con crypt() y Blowfish

En esta entrada se utilizara la función crypt() y Blowfish, otra opción muy aceptable es utilizar password_hash(), dejo este link con una explicación del tema en Login PHP con password_hash(), pasando a la función crypt() con Blowfish como algoritmo de hash lo que se busca es mejorar un método común para proteger passwords como este:
<?php
function hash_password($password, $salt) 
{
    $hash = hash_hmac('SHA512', $password, $salt);
    for ($i = 0; $i < 5000; $i++) 
    {
        $hash = hash_hmac('SHA512', $hash, $salt);
    }
    
    return $hash;
}

$nombre = $_POST['nombre'];
$password = $_POST['password'];

$salt = str_replace('=', '.', base64_encode(mcrypt_create_iv(20)));
$hash = hash_password($password, $salt);
var_dump($hash);
?>
Por una codificación mas fuerte, la función hash que utilizaremos será crypt() que soporta de forma nativa varios algoritmos. Por defecto usa la codificación MD5 pero por razones de seguridad usaremos Blowfish, también puede utilizarse Verificando si mi versión de PHP soporta Blowfish El algoritmo Blowfish está disponible a partir de la versión 5.3 de PHP. Si quieres comprobar que tienes soporte para Blowfish puedes colocar este código en un archivo PHP:
<?php
  if(defined("CRYPT_BLOWFISH") && CRYPT_BLOWFISH) {
    echo "CRYPT_BLOWFISH esta disponible";
  }
?>
Si podemos usar Blowfish pasaremos a codificar la contraseña, en el caso de que no, tendremos que instalar la última versión de PHP. Para forzar que crypt() utilice el algoritmo Blowfish debemos colocar un salt especial, si la función no reconoce el salt que escribimos, el hash resultante estará codificado en MD5. La función queda asi:
<?php
crypt($password, '$2a$10$'.$unique_salt);
?>
El segundo parámetro es un poco raro, pero en realidad lo que hay ahí son instrucciones que deben seguir la salt para Blowfish $2y$ Indica que vamos a usar BlowFish. $10$ Indica el costo del cálculo. Puede ir de 04 a 31., con 10 como valor aceptable. $unique_salt Indica un salt para el cálculo del algoritmo. Una palabra/frase alfa-numérica de 22 caractéres (por lo menos así es para Blowfish) que pueden ser ./1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz. Teniendo en cuenta todo lo anterior, un script para registrar un usuario sería más o menos así:
<?php
$nombre = $_POST['nombre'];
$password = $_POST['pass'];

// Generamos un salt aleatoreo, de 22 caracteres para Bcrypt
$salt = substr(base64_encode(openssl_random_pseudo_bytes('30')), 0, 22);

// A Crypt no le gustan los '+' así que los vamos a reemplazar por puntos.
$salt = strtr($salt, array('+' => '.')); 

// Generamos el hash
$hash = crypt($password, '$2y$10$' . $salt);

// Guardamos los datos en la base de datos
$db = new PDO(.....);
$stmt = $db->prepare('INSERT INTO usuarios (nombre, pass) VALUES (?, ?)');
$stmt->execute(array($nombre, $hash)); 
?>
Y para validar la contraseña que esta almacenada en la base de datos contra un valor proporcionado por el usuario sería algo así:
<?php
$nombre = $_POST['nombre'];
$password = $_POST['pass'];

// Validamos $nombre, bla bla bla..

// Extraemos el hash de la base de datos
$db = new PDO(......);
$stmt = $db->prepare('SELECT pass
                      FROM usuarios
                      WHERE nombre = ?');
                      
$stmt->execute(array($nombre));
$dbHash = $stmt->fetchcolumn();

// Recalculamos a ver si el hash coincide.
if (crypt($password, $dbHash) == $dbHash)
    echo 'El usuario ha sido autenticado correctamente';
else
    die('Mal Password');
?>
Creando una función que haga el trabajo Para que nuestra contraseña sea más segura, vamos a crear una función que realice un salt aleatorio. El valor que se generará aleatoriamente para cada hash serán los últimos 22 caracteres.
<?php
function crypt_blowfish($password, $digito = 7) {
$set_salt = './1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
$salt = sprintf('$2a$%02d$', $digito);
for($i = 0; $i < 22; $i++)
{
 $salt .= $set_salt[mt_rand(0, 22)];
}
return crypt($password, $salt);
}
?>
Para crear el hash con la función que recién creamos solamente tenemos que escribirlo de la siguiente manera
<?php
$password = crypt_blowfish('stringdondeestaralacontraseña');
?>
Ahora para validar la contraseña que recién se ha creado y suponiendo que se guardo en la base de datos, vamos a recuperar ese valor en la variable $passwordenBD lo que se obtiene es:
<?php
$passwordenBD = '$2a$07$yMoJrJpwEPrmVnZx4KIyNuOAiOMQksjkV1EW0YRgVe33eYe/yT60y';

//Micontraseña representa el valor capturado desde el usuario

if( crypt('micontraseña', $passwordenBD) == $passwordenBD) {
 echo 'OK';
}
?>