Jak używać bcrypt do hashowania haseł w PHP?

Co jakiś czas słyszę radę "użyj bcrypt do przechowywania haseł w PHP, Zasady bcrypt".

Ale co to jest bcrypt? PHP nie oferuje takich funkcji, Wikipedia bełkocze o narzędziu do szyfrowania plików, a Wyszukiwarki internetowe ujawniają tylko kilka implementacji Blowfish w różnych językach. Teraz Blowfish jest również dostępny w PHP poprzez mcrypt, ale jak to pomaga w przechowywaniu haseł? Blowfish jest szyfrem ogólnego przeznaczenia, działa na dwa sposoby. Gdyby mógł być zaszyfrowany, można go odszyfrować. Hasła wymagają jednokierunkowej funkcji hashowania.

Jakie jest wyjaśnienie?

Author: Peter Mortensen, 2011-01-25

9 answers

bcrypt jest algorytmem haszującym, który jest skalowalny sprzętowo (poprzez konfigurowalną liczbę rund). Jego powolność i wiele rund zapewnia, że atakujący musi wdrożyć ogromne środki i sprzęt, aby móc złamać twoje hasła. Dodaj do tego per-hasło (bcrypt wymaga soli) i możesz być pewien, że atak jest praktycznie niewykonalny bez absurdalnej ilości funduszy lub sprzętu.

bcrypt używa algorytmu Eksblowfish do hashowania hasła. Podczas gdy faza szyfrowania Eksblowfish i Blowfish są dokładnie takie same, Faza harmonogramu klucza Eksblowfish zapewnia, że każdy kolejny stan zależy zarówno od soli, jak i klucza (hasła użytkownika), a żaden stan nie może być wstępnie obliczony bez znajomości obu tych stanów. z powodu tej kluczowej różnicy, bcrypt jest jednokierunkowym algorytmem haszującym. nie można odzyskać hasła bez znajomości salt, rounds i key (hasło). [źródło ]

Jak używać bcrypt:

Using PHP > = 5.5-DEV

Funkcje hashujące hasła zostały teraz wbudowane bezpośrednio w PHP > = 5.5. Możesz teraz użyć password_hash() aby utworzyć bcrypt hash dowolnego hasła:

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

Aby zweryfikować podane przez użytkownika hasło z istniejącym Hashem, możesz użyć password_verify() jako takie:

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

Using PHP > = 5.3.7, = 5.3.3)

Istnieje biblioteka kompatybilności na GitHub stworzona na podstawie kodu źródłowego powyższych funkcji pierwotnie napisanych w języku C, który zapewnia tę samą funkcjonalność. Po zainstalowaniu biblioteki kompatybilności, użycie jest takie samo jak powyżej (minus zapis tablicy skrótów, jeśli nadal jesteś na 5.3.X Oddział).

Using PHP (DEPRECATED)

Możesz użyć funkcji crypt() do generowania hashów bcrypt ciągów wejściowych. Ta klasa może automatycznie generowanie soli i weryfikowanie istniejących hashów względem danych wejściowych. Jeśli używasz wersji PHP wyższej lub równej 5.3.7, zalecane jest użycie wbudowanej funkcji lub biblioteki compat. Ta alternatywa jest dostępna tylko w celach historycznych.

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

Możesz użyć tego kodu w następujący sposób:

$bcrypt = new Bcrypt(15);

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

Alternatywnie, możesz również użyć Portable PHP Hashing Framework .

 988
Author: Andrew Moore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-19 19:10:00

Więc, chcesz użyć bcrypt? Awesome! jednak, podobnie jak inne dziedziny kryptografii, nie powinieneś robić tego sam. Jeśli musisz martwić się o coś takiego jak zarządzanie kluczami, przechowywanie soli lub generowanie liczb losowych, robisz to źle.

Powód jest prosty: tak trywialnie łatwo jest spieprzyć bcrypt . W rzeczywistości, jeśli spojrzysz na prawie każdy fragment kodu na tej stronie, zauważysz, że narusza on co najmniej jeden z tych powszechnych problemy.

Spójrz prawdzie w oczy, kryptografia jest trudna. Zostaw to ekspertom. Zostaw to dla ludzi, którzy zajmują się utrzymaniem tych bibliotek. Jeśli musisz podjąć decyzję, robisz to źle.

Zamiast tego, po prostu użyj biblioteki. Istnieje kilka w zależności od wymagań.

Biblioteki

Oto podział niektórych z bardziej popularnych API.

PHP 5.5 API - (dostępne dla 5.3.7+)

Począwszy od PHP 5.5, nowe API do haszowania hasła są wprowadzane. Istnieje również biblioteka kompatybilności shim utrzymywana (przeze mnie) dla wersji 5.3.7+. Ma to tę zaletę, że jest recenzowana i prosta w użyciu implementacja.

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
    }
}
Naprawdę, to ma być niezwykle proste.

Zasoby:

Zend\Crypt\Password\Bcrypt (5.3.2+)

Jest to kolejne API, które jest podobne do PHP 5.5 i ma podobny cel.

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

Zasoby:

PasswordLib

Jest to nieco inne podejście do hashowania haseł. Zamiast po prostu wspierać bcrypt, PasswordLib obsługuje dużą liczbę algorytmów mieszających. Przydaje się głównie w kontekstach, w których musisz wspierać kompatybilność ze starszymi i różnymi systemami, które mogą być poza Twoją kontrolą. Obsługuje wiele algorytmów haszujących. I jest obsługiwany 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
    }
}

Bibliografia:

  • Kod Źródłowy / Dokumentacja: GitHub

PHPASS

Jest to warstwa, która obsługuje bcrypt, ale również obsługuje dość silny algorytm, który jest przydatny, jeśli nie masz dostępu do PHP > = 5.3.2... Faktycznie obsługuje PHP 3.0+ (chociaż nie z 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
    }
}

Zasoby

Uwaga: nie używaj alternatyw PHPASS, które nie są hostowane na openwall, są to różne projekty!!!

O BCrypt

Jeśli zauważysz, każda z tych bibliotek Zwraca pojedynczy ciąg znaków. To dlatego, że BCrypt działa wewnętrznie. I jest mnóstwo odpowiedzi na ten temat. Oto wybór, który napisałem, że nie będę tu kopiować/wklejać, ale link do:

Wrap Up

Istnieje wiele różnych wyborów. To, co wybierzesz, zależy od Ciebie. Jednakże, chciałbym wysoce zalecić użycie jednej z powyższych bibliotek do obsługi tego za Ciebie.

Ponownie, jeśli używasz crypt() bezpośrednio, prawdopodobnie robisz coś złego. Jeśli twój kod używa hash() (lub md5() lub sha1()) bezpośrednio, prawie na pewno robisz coś źle.

Po prostu użyj biblioteki...

 277
Author: ircmaxell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-17 16:53:19

Otrzymasz wiele informacji w Koniec z tęczowymi tabelami: co musisz wiedzieć o zabezpieczonych schematach haseł lub Portable PHP password hashing framework.

Celem jest zahaszowanie hasła czymś powolnym, więc ktoś, kto zdobędzie twoją bazę haseł, umrze, próbując je brutalnie wymusić (opóźnienie 10 ms do sprawdzenia hasła nie jest dla Ciebie niczym, dużo dla kogoś, kto próbuje je brutalnie wymusić). Bcrypt jest powolny i może być używany z parametr do wyboru, jak wolno to jest.

 45
Author: Arkh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-23 18:12:43

Możesz utworzyć jednokierunkowy hash za pomocą bcrypt używając funkcji PHP crypt() i podając odpowiednią sól Blowfish. Najważniejsze z całego równania jest to, że a) algorytm nie został skompromitowany i B) poprawnie solisz każde hasło . Nie używaj soli dla całej aplikacji; która otwiera całą aplikację do ataku z jednego zestawu tabel Rainbow.

PHP-Crypt Function

 35
Author: coreyward,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-11 08:21:07

Edit: 2013.01.15-jeśli twój serwer będzie go obsługiwał, użyj rozwiązania martinstoeckli ' ego.


Każdy chce to skomplikować. Funkcja crypt() wykonuje większość pracy.
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);
}

Przykład:

$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

Wiem, że to powinno być oczywiste, ale proszę nie używać 'password' jako hasła.

 33
Author: Jon Hulka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:59

Wersja 5.5 PHP będzie miała wbudowane wsparcie dla BCrypt, funkcje password_hash() oraz password_verify(). Właściwie to są tylko owijki wokół funkcji crypt(), i ułatwi prawidłowe korzystanie z niego. Dba o generowanie bezpiecznej losowej soli i zapewnia dobre wartości domyślne.

Najprostszym sposobem użycia tych funkcji będzie:

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

Ten kod hashuje hasło za pomocą BCrypt( algorytm 2y), generuje losową sól z losowego źródła systemu operacyjnego i używa domyślnego parametru kosztów(w tej chwili jest to 10). Druga linia sprawdza, czy wprowadzone przez użytkownika hasło pasuje do już zapisanej wartości skrótu.

Jeśli chcesz zmienić parametr kosztów, możesz to zrobić w ten sposób, zwiększając parametr kosztów o 1, podwajając czas potrzebny na obliczenie wartości skrótu:

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

W przeciwieństwie do parametru "cost", najlepiej pominąć parametr "salt", ponieważ funkcja już robi wszystko, aby stwórz bezpieczną kryptograficznie sól.

Dla PHP w wersji 5.3.7 i nowszej istnieje compatibility pack , od tego samego autora, który stworzył funkcję password_hash(). Dla wersji PHP przed 5.3.7 nie ma wsparcia dla crypt() z 2y, algorytmu unicode safe BCrypt. Można go zastąpić 2a, co jest najlepszą alternatywą dla wcześniejszych wersji PHP.

 25
Author: martinstoeckli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-16 19:34:34

Alternatywą jest użycie scrypt, specjalnie zaprojektowanego, aby być lepszym od bcrypt przez Colina Percivala wjego pracy . Istnieje rozszerzenie PHP scrypt w PECL. W idealnym przypadku algorytm ten zostałby zaimplementowany w PHP tak, aby mógł być określony dla funkcji password_* (najlepiej jako" PASSWORD_SCRYPT"), ale tego jeszcze nie ma.

 6
Author: Synchro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-19 14:17:36

Current thinking: hasze powinny być najwolniejsze, a nie najszybsze z możliwych. To tłumi Tęczowy stół ataki.

Również powiązane, ale zapobiegawcze: atakujący nigdy nie powinien mieć nieograniczonego dostępu do ekranu logowania. Aby temu zapobiec: Skonfiguruj tabelę śledzenia adresów IP, która rejestruje każde trafienie wraz z URI. Jeśli więcej niż 5 prób logowania pochodzi z tego samego adresu IP w dowolnym pięciominutowym okresie, Zablokuj z wyjaśnieniem. Drugim podejściem jest posiadanie dwupoziomowy system haseł, jak robią to banki. Umieszczenie blokady za niepowodzenia na drugim przejściu zwiększa bezpieczeństwo.

Podsumowanie: spowolnić atakującego za pomocą czasochłonnych funkcji skrótu. Ponadto Zablokuj zbyt wiele dostępu do loginu i dodaj drugą warstwę hasła.

 6
Author: FYA,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-13 17:10:07

Dla OAuth 2 hasła:

$bcrypt = new \Zend\Crypt\Password\Bcrypt;
$bcrypt->create("youpasswordhere", 10)
 3
Author: Shemeer M Ali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-23 18:16:20