1309 votes

Comment utiliser bcrypt pour hacher les mots de passe en PHP ?

De temps en temps, j'entends le conseil "Utilisez bcrypt pour stocker les mots de passe en PHP, les règles de bcrypt".

Mais qu'est-ce que bcrypt ? PHP n'offre aucune fonction de ce type, Wikipedia parle d'un utilitaire de cryptage de fichiers et les recherches sur le Web ne révèlent que quelques implémentations de la fonction de cryptage de fichiers. Blowfish dans différentes langues. Désormais, Blowfish est également disponible en PHP via mcrypt mais en quoi cela aide-t-il à stocker les mots de passe ? Blowfish est un chiffre à usage général, il fonctionne de deux façons. S'il peut être crypté, il peut être décrypté. Les mots de passe ont besoin d'une fonction de hachage à sens unique.

Quelle est l'explication ?

13 votes

Cette question a été abordé précédemment et leur suggestion d'utiliser une bibliothèque standard est excellente. La sécurité est un sujet compliqué, et en utilisant un paquetage conçu par quelqu'un qui sait ce qu'il fait, vous ne faites que vous aider.

61 votes

@eykanal - cette page ne mentionne même pas bcrypt, et encore moins l'expliquer. ce que c'est .

0 votes

C'est vrai. Cependant, j'ai inclus ce lien en me basant sur le titre de la question, à laquelle il est répondu dans cette question. En ce qui concerne les détails réels de bcrypt, vous pouvez consulter les documents suivants cet article de journal . Toutefois, sachez que votre question est très vaste ; vous demandez une explication sommaire de tout un domaine de recherche (que, je l'admets volontiers, je ne connais pas moi-même).

1100voto

Andrew Moore Points 49765

bcrypt est un algorithme de hachage qui est évolutif avec le matériel (via un nombre de tours configurable). Sa lenteur et ses multiples tours font qu'un attaquant doit déployer des fonds et du matériel massifs pour pouvoir craquer vos mots de passe. Ajoutez à cela que, pour chaque mot de passe sels ( bcrypt EXIGE des sels) et vous pouvez être sûr qu'une attaque est virtuellement irréalisable sans une quantité ridicule de fonds ou de matériel.

bcrypt utilise le Eksblowfish pour hacher les mots de passe. Alors que la phase de cryptage de Eksblowfish y Blowfish sont exactement les mêmes, la phase de programmation clé de Eksblowfish garantit que tout état ultérieur dépend à la fois du sel et de la clé (mot de passe de l'utilisateur), et qu'aucun état ne peut être précalculé sans la connaissance des deux. En raison de cette différence essentielle, bcrypt est un algorithme de hachage à sens unique. Vous ne pouvez pas récupérer le mot de passe en texte clair sans connaître le sel, les ronds et la clé (mot de passe). [ Source : ]

Comment utiliser bcrypt :

Utilisation de PHP >= 5.5-DEV

Fonctions de hachage de mot de passe ont maintenant été intégrées directement dans PHP >= 5.5 . Vous pouvez maintenant utiliser password_hash() pour créer un bcrypt hash de n'importe quel mot de passe :

<?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

Pour vérifier un mot de passe fourni par un utilisateur par rapport à un hachage existant, vous pouvez utiliser la fonction password_verify() comme tel :

<?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.';
}

Utilisation de PHP >= 5.3.7, < 5.5-DEV (également PHP RedHat >= 5.3.3)

Il existe un bibliothèque de compatibilité sur GitHub créée à partir du code source des fonctions ci-dessus écrites à l'origine en C, qui fournit la même fonctionnalité. Une fois la bibliothèque de compatibilité installée, l'utilisation est la même que ci-dessus (moins la notation abrégée des tableaux si vous êtes toujours sur la branche 5.3.x).

Utilisation de PHP < 5.3.7 (DEPRECATED)

Vous pouvez utiliser crypt() pour générer des hachages bcrypt de chaînes d'entrée. Cette classe peut générer automatiquement des sels et vérifier des hachages existants par rapport à une entrée. Si vous utilisez une version de PHP supérieure ou égale à 5.3.7, il est fortement recommandé d'utiliser la fonction intégrée ou la bibliothèque compat. . Cette alternative est fournie uniquement à des fins historiques.

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;
  }
}

Vous pouvez utiliser ce code comme ceci :

$bcrypt = new Bcrypt(15);

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

Vous pouvez également utiliser l'option Cadre de hachage PHP portable .

0 votes

@AndrewMoore Bonjour, j'ai en fait fait une question concernant mon problème, je me demandais si vous pouviez peut-être repérer quelque chose qui m'échappe ? Je suis vraiment désespéré, et c'est la seule chose que je dois faire pour aller de l'avant sur ma page de connexion ( stackoverflow.com/questions/11481199/ ) Merci beaucoup !

0 votes

@andrewliu il n'y a rien de mal avec la syntax de votre code, la seule raison pour laquelle cela ne fonctionnerait pas serait si $pass_l o $chk_pass ne sont pas ce qu'ils sont censés être ;-) c'est dommage que je continue à commenter des trucs qui s'adressent à l'autre gars ; je peux faire équipe avec toi pour t'aider si tu veux ;p

0 votes

@AndyLobel désolé, j'ai oublié d'ajouter une variable $hash_1 qui est le hachage de $pass_1 et ensuite il est censé être vérifié avec verify($hash_1, $chk_pass) déclaré par Andrew Moore à partir du commentaire ci-dessus.

307voto

ircmaxell Points 74865

Donc, vous voulez utiliser bcrypt ? Génial ! Cependant, comme dans d'autres domaines de la cryptographie, vous ne devriez pas le faire vous-même. Si vous devez vous préoccuper de quelque chose comme la gestion des clés, le stockage des sels ou la génération de nombres aléatoires, vous vous y prenez mal.

La raison est simple : il est si trivialement facile de foutre en l'air bcrypt . En fait, si vous examinez la quasi-totalité du code de cette page, vous constaterez qu'il enfreint au moins l'un de ces problèmes courants.

Admettez-le, la cryptographie est difficile.

Laissez-le aux experts. Laissez-le aux personnes dont le travail consiste à entretenir ces bibliothèques. Si vous devez prendre une décision, vous vous y prenez mal.

Au lieu de cela, il suffit d'utiliser une bibliothèque. Il en existe plusieurs, en fonction de vos besoins.

Bibliothèques

Voici une ventilation de certaines des API les plus courantes.

API PHP 5.5 - (Disponible pour 5.3.7+)

À partir de PHP 5.5, une nouvelle API pour le hachage des mots de passe est introduite. Il y a aussi une bibliothèque de compatibilité shim maintenue (par moi) pour 5.3.7+. Cette bibliothèque a l'avantage d'être évaluée par des pairs et d'avoir été mise à jour. simple pour utiliser la mise en œuvre.

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
    }
}

En réalité, l'objectif est d'être extrêmement simple.

Ressources :

Zend \Crypt\Password\Bcrypt (5.3.2+)

C'est une autre API qui est similaire à celle de PHP 5.5, et qui a un but similaire.

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
    }
}

Ressources :

PasswordLib

Il s'agit d'une approche légèrement différente du hachage des mots de passe. Plutôt que de supporter simplement bcrypt, PasswordLib supporte un grand nombre d'algorithmes de hachage. C'est principalement utile dans les contextes où vous devez assurer la compatibilité avec des systèmes anciens et disparates qui peuvent être hors de votre contrôle. Il prend en charge un grand nombre d'algorithmes de hachage. Et est supporté à partir de la version 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
    }
}

Références :

  • Code source / Documentation : GitHub

PHPASS

C'est une couche qui supporte bcrypt, mais aussi un algorithme assez fort qui est utile si vous n'avez pas accès à PHP >= 5.3.2... Elle supporte en fait PHP 3.0+ (mais pas avec 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
    }
}

Ressources

Note : N'utilisez pas les alternatives de PHPASS qui ne sont pas hébergées sur openwall, ce sont des projets différents ! !!

À propos de BCrypt

Si vous remarquez, chacune de ces bibliothèques renvoie une seule chaîne de caractères. C'est à cause de la façon dont BCrypt fonctionne en interne. Et il y a une TONNE de réponses à ce sujet. Voici une sélection de celles que j'ai écrites, que je ne copierai pas/collerai ici, mais dont je ferai le lien :

Récapitulation

Il existe de nombreux choix différents. C'est à vous de voir lequel vous choisissez. Cependant, je voudrais HAUTEMENT Nous vous recommandons d'utiliser l'une des bibliothèques ci-dessus pour gérer cela pour vous.

Encore une fois, si vous utilisez crypt() directement, vous faites probablement quelque chose de mal. Si votre code utilise hash() (ou md5() o sha1() ) directement, vous êtes presque certainement en train de faire quelque chose de mal.

Il suffit d'utiliser une bibliothèque...

7 votes

Le sel doit être généré de manière aléatoire, mais il n'est pas nécessaire qu'il provienne d'une source aléatoire sécurisée. Le sel n'est pas un secret . Le fait de pouvoir deviner le prochain sel n'a pas de réel impact sur la sécurité ; tant qu'ils proviennent d'un pool de données suffisamment large pour générer des sels différents pour chaque mot de passe encodé, tout va bien. N'oubliez pas que le sel est là pour empêcher l'utilisation des tables arc-en-ciel si vos hachages tombent entre de mauvaises mains. Ils ne sont pas secrets.

7 votes

@AndrewMoore absolument correct ! Cependant, le sel doit avoir suffisamment d'entropie pour être statistiquement unique. Pas seulement dans votre application, mais dans toutes les applications. Donc mt_rand() a une période assez élevée, mais la valeur de la graine n'est que de 32 bits. Ainsi, en utilisant mt_rand() vous limite effectivement à seulement 32 bits d'entropie. Ce qui, grâce au problème de l'anniversaire, signifie que vous avez 50% de chances de collision à seulement 7k sels générés (globalement). Puisque bcrypt accepte 128 bits de sel, il est préférable d'utiliser une source qui peut fournir les 128 bits ;-). (à 128 bits, 50% de chance de collision se produit à 2e19 hashs)...

1 votes

@ircmaxell : Il s'agit d'un "pool de données suffisamment grand". Cependant, votre source n'a pas besoin d'être une source à TRÈS HAUTE entropie, juste assez élevée pour les 128 bits. Cependant, si vous avez épuisé toutes vos sources disponibles (vous n'avez pas OpenSSL, etc...) et que votre seule solution de repli est mt_rand(), c'est toujours mieux que l'alternative (qui est rand()).

47voto

Arkh Points 5804

Vous trouverez beaucoup d'informations dans Assez avec les tables arc-en-ciel : Ce que vous devez savoir sur les systèmes de mots de passe sécurisés o Cadre portable de hachage de mots de passe en PHP .

Le but est de hacher le mot de passe avec quelque chose de lent, de sorte que quelqu'un qui obtient votre base de données de mots de passe mourra en essayant de le forcer (un délai de 10 ms pour vérifier un mot de passe n'est rien pour vous, beaucoup pour quelqu'un qui essaie de le forcer). Bcrypt est lent et peut être utilisé avec un paramètre pour choisir son degré de lenteur.

0 votes

@Arkh : Le ralentissement de la vitesse d'exécution n'est pas un problème. La force brutale ne fonctionnera que si vous n'avez pas réussi, en tant que développeur, à appliquer des politiques de mot de passe fortes.

7 votes

Appliquez ce que vous voulez, mais les utilisateurs finiront par se tromper et par utiliser le même mot de passe pour plusieurs choses. Vous devez donc le protéger autant que possible ou mettre en œuvre quelque chose qui vous permet de ne pas stocker de mot de passe (SSO, openID, etc.).

2 votes

Vous pouvez plus facilement empêcher une attaque par mot de passe brutale en bloquant les "utilisateurs" qui ne parviennent pas à deviner leur mot de passe n fois en une journée, où n est supérieur à un nombre raisonnable d'essais, comme 50.

36voto

coreyward Points 26109

Vous pouvez créer un hachage à sens unique avec bcrypt en utilisant la fonction PHP crypt() et en passant dans un sel Blowfish approprié. Le plus important dans toute cette équation est que A) l'algorithme n'a pas été compromis et B) vous salez correctement chaque mot de passe . N'utilisez pas de sel pour l'ensemble de l'application, car cela expose l'ensemble de votre application à une attaque à partir d'un seul ensemble de tables Rainbow.

PHP - Fonction Crypt

4 votes

C'est la bonne approche : utilisez la méthode de PHP. crypt() qui prend en charge plusieurs fonctions de hachage de mot de passe différentes. Assurez-vous que vous n'utilisez pas CRYPT_STD_DES o CRYPT_EXT_DES - n'importe lequel des autres types supportés convient (y compris bcrypt, sous le nom de CRYPT_BLOWFISH ).

4 votes

Le SHA dispose également d'un paramètre de coût, via l'option "rounds". En utilisant cette option, je ne vois pas non plus de raison de favoriser bcrypt.

3 votes

En fait, un simple SHA-1 (ou MD5) d'un mot de passe est toujours facilement forcé par brute, avec ou sans sel (le sel aide contre les tables arc-en-ciel, pas contre le forçage brutal). Utilisez bcrypt.

34voto

Jon Hulka Points 867

Edit : 2013.01.15 - Si votre serveur le supporte, utilisez la solution de martinstoeckli à la place.


Tout le monde veut rendre les choses plus compliquées qu'elles ne le sont. La fonction crypt() fait le gros du travail.

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);
}

Exemple :

$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

Je sais que cela devrait être évident, mais n'utilisez pas "password" comme mot de passe.

3 votes

La création du sel pourrait être améliorée (utiliser la source aléatoire de l'OS), sinon cela me semble bien. Pour les nouvelles versions de PHP, il est préférable d'utiliser 2y au lieu de 2a .

0 votes

Utiliser mcrypt_create_iv($size, MCRYPT_DEV_URANDOM) comme source de sel.

0 votes

Je regarderai de plus près mcrypt_create_iv() quand j'aurai un moment, au moins cela devrait améliorer légèrement les performances.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X