H2 - P2019

Développement web

Cours 24 - 2016-03-14

PHP

Passwords

Si votre site permet aux visiteurs de s'enregistrer et de se connecter, vous allez devoir gérer des mots de passe

Cette donnée est l'une des plus sensibles que vous pourriez avoir à stocker (avec les coordonnées bancaires)

Il est nécessaire de respecter un minimum de sécurité

Nous allons simuler une connexion classique à un site avec email et mot de passe

Inscription / Login

Inscription

(Sans validation par email)

Login

Lorsque vous renvoyer une erreur, ne donnez pas trop d'information

Nous allons nous baser sur une base de données simple

Créer une base de données et executer la requête SQL suivante


CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `email` varchar(250) NOT NULL,
  `password` varchar(250) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
                    

Nous n'allons pas voir la partie formulaire.

Admettons que les données soient envoyées en POST et qu'elles n'aient pas besoin d'être vérifiées

Le code d'inscription ressemblerait à ça


// Retrieve inputs
$email    = $_POST['email'];
$password = $_POST['password'];

// SQL query
$prepare = $pdo->prepare('INSERT INTO users (email,password) VALUES (:email,:password)');
$prepare->bindValue(':email',$email);
$prepare->bindValue(':password',$password);
$exec = $prepare->execute();
                    

Et le code de login ressemblerait à ça


// Retrieve inputs
$email    = $_POST['email'];
$password = $_POST['password'];

// SQL query
$prepare = $pdo->prepare('SELECT * FROM users WHERE email = :email LIMIT 1');
$prepare->bindValue(':email',$email);
$query = $prepare->execute();
$user  = $prepare->fetch();

// Test password
if($user->password == $password)
    echo 'You shall pass';
else
    echo 'You shall not pass';
                    
Problème 1

Mot de passe en clair

Ici, le mot de passe est sauvegardé en clair

N'importe quel individu ayant accès à la base de données (administrateur ou hacker) pourra voir le mot de passe de chaque utilisateur

Solution 1

Hasher

  • Encrypter : Transformer une donnée pour la rendre méconnaissable, mais réversiblement

  • Hasher : Transformer une donnée pour la rendre méconnaissable, mais irréversiblement

La solution consiste à hasher le mot de passe pour le rendre méconnaissable

Pour cela nous allons utiliser la fonction hash()


echo hash('sha256','monmotdepasse');

// Ce qui affichera
// a7af71ad2b0ce07c36781ab7c8a6d36bd703824c22647f85d6de62063b219bc6
                    
  • Le premier paramètre correspond à l'algorithme de hash
  • Le deuxième paramètre correspond à la variable à hasher

Un même algorithme avec une même chaîne de caractères renvera toujours le même résultat


echo hash('sha256','azerty'); // f2d81a260dea8a100dd517984e53c56a7523d96942a834b9cdc249bd4e8c7aa9
echo hash('sha256','azerty'); // f2d81a260dea8a100dd517984e53c56a7523d96942a834b9cdc249bd4e8c7aa9
echo hash('sha256','azerty'); // f2d81a260dea8a100dd517984e53c56a7523d96942a834b9cdc249bd4e8c7aa9
echo hash('sha256','azerty'); // f2d81a260dea8a100dd517984e53c56a7523d96942a834b9cdc249bd4e8c7aa9
                    

Il existe de nombreux algorithmes de hash

Certains sont lents, certains peuvent être décryptés (md5)

SHA-256 est rapide, n'a jamais été décrypté et est très populaire

Notre code d'inscription deviendra donc


// Retrieve inputs
$email    = $_POST['email'];
$password = hash('sha256',$_POST['password']); // Hash

// SQL query
$prepare = $pdo->prepare('INSERT INTO users (email,password) VALUES (:email,:password)');
$prepare->bindValue('email',$email);
$prepare->bindValue('password',$password);
$prepare->execute();
                    

Notre code de login deviendra donc


// Retrieve inputs
$email    = $_POST['email'];
$password = hash('sha256',$_POST['password']); // Hash

// SQL query
$prepare = $pdo->prepare('SELECT * FROM users WHERE email = :email LIMIT 1');
$prepare->bindValue('email',$email);
$prepare->execute();
$user = $prepare->fetch();

// Test password
if($user->password == $password)
    echo 'You shall pass !';
else
    echo 'You shall not pass !';
                    
Problème 2

Rainbow tables

Hasher, c'est pas mal, mais insuffisant

Une rainbow table contient une grande quantité de mots de passes "classiques" et leurs versions hashés

Si le visiteur a utilisé un de ces mots de passe, il sera facile de le retrouver dans la rainbow table à partir de la version encryptée

Solution 2

Salt

Rajouter quelques caractères au mot de passe pour être certain qu'il n'existe pas dans les rainbow tables

Le mot de passe azerty deviendra par exemple 76t!"ed#azerty

Cette technique s'appelle le salage (salt)

Notre code d'inscription deviendra donc


// Retrieve inputs
$email    = $_POST['email'];
$password = hash('sha256',SALT.$_POST['password']); // Hash + Salt

// SQL query
$prepare = $pdo->prepare('INSERT INTO users (email,password) VALUES (:email,:password)');
$prepare->bindValue('email',$email);
$prepare->bindValue('password',$password);
$prepare->execute();
                    

Notre code de login deviendra donc


// Retrieve inputs
$email    = $_POST['email'];
$password = hash('sha256',SALT.$_POST['password']); // Hash + Salt

// SQL query
$prepare = $pdo->prepare('SELECT * FROM users WHERE email = :email LIMIT 1');
$prepare->bindValue('email',$email);
$prepare->execute();
$user = $prepare->fetch();

// Test password
if($user->password == $password)
    echo 'You shall pass !';
else
    echo 'You shall not pass !';
                    

Il s'agit du minimum pour stocker des mots de passes

Il existe bien d'autres méthodes et le salt peut être amélioré
(salt unique par ligne dans la BDD)

Si vous travaillez sur un projet et constatez que les mots de passes sont stockés en clair, corrigez le problème ou avertissez le responsable.

Aller plus loin :

http://www.sitepoint.com/password-hashing-in-php/ http://code.tutsplus.com/tutorials/understanding-hash-func...