Cómo usar el password_hash de PHP para hacer hash y verificar las contraseñas

php salt password-hash php-password-hash


Recientemente he estado tratando de implementar mi propia seguridad en un script de inicio de sesión con el que me topé en Internet. Después de luchar por tratar de aprender cómo hacer mi propio script para generar una sal para cada usuario, me topé con password_hash .

Por lo que entiendo (basado en la lectura de esta página ), la sal ya se genera en la fila cuando usas password_hash . ¿Es esto cierto?

Otra pregunta que tenía era,¿no sería inteligente tener 2 sales? ¿Uno directamente en el archivo y otro en la base de datos? De esa manera,si alguien compromete tu sal en la base de datos,¿todavía tienes la que está directamente en el archivo? Leí aquí que almacenar sales nunca es una idea inteligente,pero siempre me confundió lo que la gente quería decir con eso.




Answer 1 Akar


Usar password_hash es la forma recomendada de almacenar contraseñas. No los separe a DB y archivos.

Digamos que tenemos la siguiente entrada:

$password = $_POST['password'];

No valido la entrada sólo para entender el concepto.

Primero se obtiene la contraseña haciendo esto:

$hashed_password = password_hash($password, PASSWORD_DEFAULT);

Entonces vea la salida:

var_dump($hashed_password);

Como puedes ver,es un desastre.(Asumo que hiciste esos pasos).

Ahora almacena esta hashed_password en su base de datos,asegurándose de que su columna de contraseñas es lo suficientemente grande como para contener el valor hashed (al menos 60 caracteres o más).Cuando un usuario le pide que se registre,comprueba la entrada de la contraseña con este valor hash en la base de datos,haciendo esto:

// Query the database for username and password
// ...

if(password_verify($password, $hashed_password)) {
    // If the password inputs matched the hashed password in the database
    // Do something, you know... log them in.
} 

// Else, Redirect them back to the login page.

Referencia oficial




Answer 2 martinstoeckli


Si lo has entendido bien,la función password_hash()generará una sal por sí misma,y la incluye en el valor hash resultante.Almacenar la sal en la base de datos es absolutamente correcto,hace su trabajo incluso si se conoce.

// Hash a new password for storing in the database.
// The function automatically generates a cryptographically safe salt.
$hashToStoreInDb = password_hash($_POST['password'], PASSWORD_DEFAULT);

// Check if the hash of the entered login password, matches the stored hash.
// The salt and the cost factor will be extracted from $existingHashFromDb.
$isPasswordCorrect = password_verify($_POST['password'], $existingHashFromDb);

La segunda sal que mencionaste (la que está almacenada en un archivo),es en realidad una pimienta o una llave de lado del servidor.Si la añades antes de la sal,como la sal,entonces añades una pimienta.Hay una mejor manera,sin embargo,primero podrías calcular el hash,y después encriptarlo (de dos maneras)con una clave del lado del servidor.Esto te da la posibilidad de cambiar la clave cuando sea necesario.

A diferencia de la sal,esta llave debe mantenerse en secreto.La gente a menudo la mezcla y trata de ocultar la sal,pero es mejor dejar que la sal haga su trabajo y añadir el secreto con una llave.




Answer 3 Joel Hinz


Sí,es verdad.¿Por qué dudas del faq php sobre la función? :)

El resultado de ejecutar password_hash() tiene cuatro partes:

  1. el algoritmo utilizado
  2. parameters
  3. salt
  4. contraseña real hash

Así que como puedes ver,el hachís es parte de ello.

Claro,podrías tener una sal adicional para una capa de seguridad añadida,pero honestamente creo que eso es exagerado en una aplicación php regular.El algoritmo bcrypt por defecto es bueno,y el opcional del pez globo es posiblemente aún mejor.




Answer 4 Mahesh Yadav


Nunca uses md5()para asegurar tu contraseña,ni siquiera con sal,¡siempre es peligroso!!

Asegure su contraseña con los últimos algoritmos hash como se indica a continuación.

<?php

// Your original Password
$password = '121@121';

//PASSWORD_BCRYPT or PASSWORD_DEFAULT use any in the 2nd parameter
/*
PASSWORD_BCRYPT always results 60 characters long string.
PASSWORD_DEFAULT capacity is beyond 60 characters
*/
$password_encrypted = password_hash($password, PASSWORD_BCRYPT);

?>

Para cotejar con la contraseña cifrada de la base de datos y la contraseña introducida por el usuario,utilice la siguiente función.

<?php 

if (password_verify($password_inputted_by_user, $password_encrypted)) {
    // Success!
    echo 'Password Matches';
}else {
    // Invalid credentials
    echo 'Password Mismatch';
}

?>

Si quieres usar tu propia sal,usa tu función generada a medida para la misma,sólo sigue abajo,pero no recomiendo esto ya que se encuentra desactualizada en las últimas versiones de PHP.

lea este http://php.net/manual/en/function.password-hash.php antes de usar el siguiente código.

<?php

$options = [
    'salt' => your_custom_function_for_salt(), 
    //write your own code to generate a suitable & secured salt
    'cost' => 12 // the default cost is 10
];

$hash = password_hash($your_password, PASSWORD_DEFAULT, $options);

?>

¡¡Espero que todo esto ayude!!




Answer 5 Sammitch


Hay una clara falta de discusión sobre la compatibilidad hacia atrás y hacia delante que está incorporada en las funciones de contraseña de PHP.Notablemente:

  1. Compatibilidad con versiones anteriores: las funciones de contraseña son esencialmente un contenedor bien escrito alrededor de crypt() , y son inherentemente compatibles con hashes de formato crypt() , incluso si utilizan algoritmos hash obsoletos y / o inseguros.
  2. Compatibilidad hacia adelante: la inserción de password_needs_rehash() y un poco de lógica en su flujo de trabajo de autenticación puede mantenerlo actualizado con algoritmos actuales y futuros con posibles cambios futuros en el flujo de trabajo. Nota: Cualquier cadena que no coincida con el algoritmo especificado se marcará por necesitar una repetición, incluidos los hashes no compatibles con criptas.

Eg:

class FakeDB {
    public function __call($name, $args) {
        printf("%s::%s(%s)\n", __CLASS__, $name, json_encode($args));
        return $this;
    }
}

class MyAuth {
    protected $dbh;
    protected $fakeUsers = [
        // old crypt-md5 format
        1 => ['password' => '$1$AVbfJOzY$oIHHCHlD76Aw1xmjfTpm5.'],
        // old salted md5 format
        2 => ['password' => '3858f62230ac3c915f300c664312c63f', 'salt' => 'bar'],
        // current bcrypt format
        3 => ['password' => '$2y$10$3eUn9Rnf04DR.aj8R3WbHuBO9EdoceH9uKf6vMiD7tz766rMNOyTO']
    ];

    public function __construct($dbh) {
        $this->dbh = $dbh;
    }

    protected function getuser($id) {
        // just pretend these are coming from the DB
        return $this->fakeUsers[$id];
    }

    public function authUser($id, $password) {
        $userInfo = $this->getUser($id);

        // Do you have old, turbo-legacy, non-crypt hashes?
        if( strpos( $userInfo['password'], '$' ) !== 0 ) {
            printf("%s::legacy_hash\n", __METHOD__);
            $res = $userInfo['password'] === md5($password . $userInfo['salt']);
        } else {
            printf("%s::password_verify\n", __METHOD__);
            $res = password_verify($password, $userInfo['password']);
        }

        // once we've passed validation we can check if the hash needs updating.
        if( $res && password_needs_rehash($userInfo['password'], PASSWORD_DEFAULT) ) {
            printf("%s::rehash\n", __METHOD__);
            $stmt = $this->dbh->prepare('UPDATE users SET pass = ? WHERE user_id = ?');
            $stmt->execute([password_hash($password, PASSWORD_DEFAULT), $id]);
        }

        return $res;
    }
}

$auth = new MyAuth(new FakeDB());

for( $i=1; $i<=3; $i++) {
    var_dump($auth->authuser($i, 'foo'));
    echo PHP_EOL;
}

Output:

MyAuth::authUser::password_verify
MyAuth::authUser::rehash
FakeDB::prepare(["UPDATE users SET pass = ? WHERE user_id = ?"])
FakeDB::execute([["$2y$10$zNjPwqQX\/RxjHiwkeUEzwOpkucNw49yN4jjiRY70viZpAx5x69kv.",1]])
bool(true)

MyAuth::authUser::legacy_hash
MyAuth::authUser::rehash
FakeDB::prepare(["UPDATE users SET pass = ? WHERE user_id = ?"])
FakeDB::execute([["$2y$10$VRTu4pgIkGUvilTDRTXYeOQSEYqe2GjsPoWvDUeYdV2x\/\/StjZYHu",2]])
bool(true)

MyAuth::authUser::password_verify
bool(true)

Como nota final,dado que sólo se puede rehacer la contraseña de un usuario al iniciar sesión,debería considerar la "puesta de sol" como un legado inseguro para proteger a sus usuarios.Con esto quiero decir que después de un cierto período de gracia,se eliminan todos los hashes inseguros [por ejemplo:MD5/SHA desnudo/de otro modo débil]y los usuarios confían en los mecanismos de restablecimiento de la contraseña de su aplicación.