web-dev-qa-db-fra.com

Comment utilisez-vous bcrypt pour le hachage de mots de passe en PHP?

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

Mais qu'est-ce que bcrypt? PHP n'offre aucune de ces fonctions, Wikipedia parle d'un utilitaire de chiffrement de fichiers et les recherches sur le Web ne révèlent que quelques implémentations de Blowfish dans différentes langues. Maintenant, Blowfish est également disponible dans PHP via mcrypt, mais en quoi cela aide-t-il à stocker les mots de passe? Blowfish est un chiffrement à usage général, il fonctionne de deux manières. S'il peut être chiffré, il peut être déchiffré. Les mots de passe nécessitent une fonction de hachage à sens unique.

Quelle est l'explication?

1219
Vilx-

bcrypt est un algorithme de hachage é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 déchiffrer vos mots de passe. Ajoutez à cela par mot de passe sels (bcrypt REQUIRE sels) et vous pouvez être sûr qu’une attaque est pratiquement irréalisable sans fonds ni matériel ridicules.

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

Comment utiliser bcrypt:

Utiliser PHP> = 5.5-DEV

Fonctions de hachage du mot de passe ont maintenant été construites directement dans PHP> = 5.5 . Vous pouvez maintenant utiliser password_hash() pour créer un hash bcrypt 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 le mot de passe fourni par l'utilisateur par rapport à un hachage existant, vous pouvez utiliser le password_verify() en tant que 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 RedHat PHP> = 5.3.3)

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

Utiliser PHP <5.3.7 (DEPRECATED)

Vous pouvez utiliser la fonction 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 les hachages existants par rapport à une entrée. Si vous utilisez une version de PHP supérieure ou égale à 5.3.7, il est vivement 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);

Sinon, vous pouvez également utiliser le Portable PHP Hashing Framework .

1037
Andrew Moore

Alors, vous voulez utiliser bcrypt? Génial! Cependant, à l'instar 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 de sels ou la génération de nombres aléatoires, vous le faites mal.

La raison est simple: il est si facile de manipuler bcrypt . En fait, si vous regardez presque chaque morceau de code sur cette page, vous remarquerez qu'il enfreint au moins un de ces problèmes courants.

Face It, la cryptographie est difficile.

Laissez-le aux experts. Laissez aux personnes chargées de la maintenance de ces bibliothèques. Si vous devez prendre une décision, vous le faites mal.

Au lieu de cela, utilisez simplement une bibliothèque. Plusieurs existent selon vos besoins.

Bibliothèques

Voici un aperçu de certaines des API les plus courantes.

API PHP 5.5 Disponible pour 5.3.7+)

À partir de PHP 5.5, une nouvelle API de hachage de mots de passe est en cours d’introduction. Il existe également une bibliothèque de compatibilité shim gérée (par moi) pour 5.3.7+. Cela présente l’avantage d’être une mise en œuvre contrôlée par les pairs et simple.

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

Vraiment, cela doit être extrêmement simple.

Ressources:

Zend\Crypt\Password\Bcrypt (5.3.2+)

Ceci est une autre API similaire à celle de PHP 5.5, et remplit un objectif 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

C'est une approche légèrement différente du hachage de mot de passe. Plutôt que de simplement prendre en charge bcrypt, PasswordLib prend en charge un grand nombre d'algorithmes de hachage. Il est principalement utile dans les contextes dans lesquels vous devez prendre en charge la compatibilité avec des systèmes hérités et disparates pouvant échapper à votre contrôle. Il supporte un grand nombre d'algorithmes de hachage. Et est pris en charge 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/Documentation source: GitHub

PHPASS

C'est une couche qui supporte bcrypt, mais supporte également un algorithme assez puissant qui est utile si vous n'avez pas accès à PHP> = 5.3.2 ... Il 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

Remarque: N'utilisez pas les alternatives 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. Cela est dû au fonctionnement interne de BCrypt. Et il y a une tonne de réponses à ce sujet. Voici une sélection que j'ai écrite, que je ne vais pas copier/coller ici, mais un lien vers:

Emballer

Il y a beaucoup de choix différents. Ce que vous choisissez est à vous. Cependant, je vous VIVEMENT vous recommande d'utiliser l'une des bibliothèques ci-dessus pour le gérer à votre place.

Encore une fois, si vous utilisez crypt() directement, vous faites probablement quelque chose de mal. Si votre code utilise hash() (ou md5() ou sha1()) directement, vous faites certainement une erreur.

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

289
ircmaxell

Vous obtiendrez de nombreuses informations dans Assez avec les tables Rainbow: Ce que vous devez savoir sur les schémas de mots de passe sécurisés ou Portable PHP framework de hachage de mot de passe.

Le but est de hacher le mot de passe avec quelque chose de lent, de sorte que quelqu'un qui récupérera votre base de données de mot de passe mourra en essayant de le forcer brutalement (un délai de 10 ms pour vérifier un mot de passe ne sert à rien, beaucoup pour quelqu'un qui tente de le forcer brutalement). Bcrypt est lent et peut être utilisé avec un paramètre pour choisir sa lenteur.

46
Arkh

Vous pouvez créer un hachage à sens unique avec bcrypt en utilisant la fonction crypt() de PHP et en transmettant le sel Blowfish approprié. Le plus important de toute l'é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 à l'échelle de l'application. cela ouvre toute votre application pour attaquer à partir d'un seul ensemble de tables Rainbow.

PHP - Fonction Crypt

35
coreyward

Edit: 2013.01.15 - Si votre serveur le prend en charge, utilisez plutôt solution de martinstoeckli .


Tout le monde veut rendre cela plus compliqué qu'il ne l'est. La fonction crypt () effectue la majeure partie 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 "mot de passe" comme mot de passe.

33
Jon Hulka

La version 5.5 de PHP aura un support intégré pour BCrypt, les fonctions password_hash() et password_verify() . En réalité, ce ne sont que des wrappers autour de la fonction crypt() , et faciliteront son utilisation correcte. Il prend en charge la génération d'un sel aléatoire sûr et fournit de bonnes valeurs par défaut.

Le moyen le plus simple d'utiliser ces fonctions sera:

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

Ce code hachera le mot de passe avec BCrypt (algorithme 2y), générera un sel aléatoire à partir de la source aléatoire du système d'exploitation et utilisera le paramètre de coût par défaut (pour le moment, il s'agit de 10). La deuxième ligne vérifie si le mot de passe entré par l'utilisateur correspond à une valeur de hachage déjà stockée.

Si vous souhaitez modifier le paramètre de coût, procédez comme suit: en augmentant le paramètre de coût de 1, vous doublez le temps nécessaire pour calculer la valeur de hachage:

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

Contrairement au paramètre "cost", il est préférable d’omettre le paramètre "salt", car la fonction fait déjà de son mieux pour créer un sel sûr du point de vue cryptographique.

Pour PHP version 5.3.7 et ultérieure, il existe un pack de compatibilité , du même auteur qui a créé la fonction password_hash(). Pour PHP versions antérieures à la version 5.3.7, crypt() avec 2y, l'algorithme Unryptode sûr de BCrypt, n'est pas pris en charge. On pourrait le remplacer à la place par 2a, qui constitue la meilleure alternative pour les versions antérieures de PHP.

27
martinstoeckli

Une alternative consiste à utiliser scrypt, spécialement conçu pour être supérieur à bcrypt par Colin Percival dans son article . Il existe une extension scrypt PHP dans PECL . Idéalement, cet algorithme serait intégré à PHP afin qu'il puisse être spécifié pour les fonctions password_ * (idéalement sous la forme "PASSWORD_SCRYPT"), mais il n'en est pas encore là.

6
Synchro

Pensée actuelle: les hachages devraient être les plus lents disponibles, pas les plus rapides possibles. Ceci supprime Rainbow table attaques.

Egalement lié, mais à titre préventif: Un attaquant ne devrait jamais avoir un accès illimité à votre écran de connexion. Pour éviter cela: Configurez une table de suivi des adresses IP qui enregistre chaque hit avec l'URI. Si plus de 5 tentatives de connexion proviennent de la même adresse IP au cours d'une période de cinq minutes, bloquez avec une explication. Une approche secondaire consiste à utiliser un système de mot de passe à deux niveaux, comme le font les banques. Le verrouillage des défaillances lors de la deuxième passe renforce la sécurité.

Résumé: ralentissez l'attaquant en utilisant des fonctions de hachage fastidieuses. En outre, bloquez trop d’accès à votre connexion et ajoutez un deuxième niveau de mot de passe.

6
FYA

Pour OAuth 2 mots de passe:

$bcrypt = new \Zend\Crypt\Password\Bcrypt;
$bcrypt->create("youpasswordhere", 10)
3
Shemeer M Ali