Как вы используете bcrypt для хэширования паролей в PHP?

1151

Время от времени я слышу совет "Использовать bcrypt для хранения паролей в PHP, правила bcrypt".

Но что такое bcrypt? PHP не предлагает таких функций, Wikipedia болтает о утилите шифрования файлов и веб-поиске, просто раскрывает несколько реализаций Blowfish в разных языки. Теперь Blowfish также доступен на PHP через mcrypt, но как это помогает при хранении паролей? Blowfish - это шифр общего назначения, он работает двумя способами. Если он может быть зашифрован, его можно расшифровать. Пароли нуждаются в односторонней хэш-функции.

Какое объяснение?

  • 12
    Этот вопрос был рассмотрен ранее , и их предложение использовать стандартную библиотеку превосходно. Безопасность - это сложный вопрос, и, используя пакет, разработанный кем-то, кто знает, какого черта они делают, вы только помогаете себе.
  • 56
    @eykanal - на этой странице даже не упоминается bcrypt, а тем более объясните, что это такое .
Показать ещё 5 комментариев
Теги:
cryptography
passwords
bcrypt
password-protection

9 ответов

928
Лучший ответ

bcrypt - это алгоритм хэширования, который масштабируется с помощью аппаратного обеспечения (через настраиваемое количество раундов). Его медленность и несколько раундов гарантирует, что злоумышленник должен развернуть огромные средства и оборудование, чтобы взломать ваши пароли. Добавьте к этому пароль соли (bcrypt ТРЕБУЕТ соли), и вы можете быть уверены, что атака практически невозможна без какой-либо смехотворной суммы средств или аппаратное обеспечение.

bcrypt использует алгоритм Eksblowfish для хэш-паролей. Хотя фаза шифрования Eksblowfish и Blowfish абсолютно одинакова, ключевая фаза расписания Eksblowfish гарантирует, что любое последующее состояние зависит как от соли, так и от ключа (пароль пользователя), и никакое состояние не может быть предварительно вычислено без знания обоим. Из-за этого ключевого различия bcrypt является односторонним алгоритмом хеширования. Вы не можете получить пароль обычного текста, не зная соли, раунды и клавишу (пароль), [Источник]

Как использовать bcrypt:

Использование PHP >= 5.5-DEV

Функции хеширования паролей теперь построены непосредственно в PHP >= 5.5. Теперь вы можете использовать password_hash() для создания хеша bcrypt любого пароля:

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

Чтобы проверить пароль, предоставленный пользователем для существующего хеша, вы можете использовать password_verify():

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

Использование PHP >= 5.3.7, < 5.5-DEV (также RedHat PHP >= 5.3.3)

Существует библиотека совместимости на GitHub созданный на основе исходного кода вышеупомянутых функций, первоначально написанных на C, который обеспечивает ту же функциональность. Как только библиотека совместимости установлена, использование будет таким же, как указано выше (минус обозначение сокращенного массива, если вы все еще находитесь в ветке 5.3.x).

Использование PHP < 5.3.7 (DEPRECATED)

Вы можете использовать функцию crypt() для генерации хэшей bcrypt входных строк. Этот класс может автоматически генерировать соли и проверять существующие хэши на вход. Если вы используете версию PHP выше или равную 5.3.7, настоятельно рекомендуется использовать встроенную функцию или библиотеку совместимости. Эта альтернатива предоставляется только в исторических целях.

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

Вы можете использовать этот код следующим образом:

$bcrypt = new Bcrypt(15);

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

В качестве альтернативы вы также можете использовать Portable PHP Hashing Framework.

  • 0
    Ваша реализация, кажется, segfault, когда третий вариант для случайной генерации байтов. Я посмотрел на php.net/crypt, и один из лучших вариантов генерации случайных байтов был невероятно прост. Взгляните на мою переписать: gist.github.com/1070401
  • 1
    @ Злая Блоха: mt_rand() нельзя использовать в контекстах, чувствительных к безопасности. Кроме того, этот код использовался во многих средах без каких-либо проблем, и ни один PHP-скрипт не должен segfault . Возможно, вы захотите изучить перекомпиляцию PHP в вашей среде.
Показать ещё 74 комментария
288

Итак, вы хотите использовать bcrypt? Awesome! Однако, как и в других областях криптографии, вы не должны делать это сами. Если вам нужно беспокоиться о чем-либо вроде управления ключами или хранении солей или генерации случайных чисел, вы делаете это неправильно.

Причина проста: она настолько тривиально легка для испортить bcrypt. Фактически, если вы посмотрите почти на каждый фрагмент кода на этой странице, вы заметите, что он нарушает хотя бы одну из этих общих проблем.

Face It, криптография сложна.

Оставьте это для экспертов. Оставьте это для людей, которые работают, чтобы поддерживать эти библиотеки. Если вам нужно принять решение, вы делаете это неправильно.

Вместо этого просто используйте библиотеку. Некоторые из них существуют в зависимости от ваших требований.

Библиотеки

Ниже приведена разбивка некоторых наиболее распространенных API.

PHP 5.5 API - (доступно для 5.3.7 +)

Начиная с PHP 5.5, вводится новый API для хеширования паролей. Существует также библиотека совместимости прокладок, которая поддерживается мной (5.3). Это может быть рецензируемой и простой в использовании.

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

Действительно, это было очень просто.

Ресурсы

Zend\Crypt\Password\Bcrypt (5.3.2 +)

Это еще один API, похожий на PHP 5.5, и делает аналогичную цель.

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

Ресурсы

PasswordLib

Это немного другой подход к хешированию паролей. Вместо того, чтобы просто поддерживать bcrypt, PasswordLib поддерживает большое количество алгоритмов хеширования. Это в основном полезно в контексте, где вам необходимо поддерживать совместимость с устаревшими и разрозненными системами, которые могут быть вне вашего контроля. Он поддерживает большое количество алгоритмов хеширования. И поддерживается 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
    }
}

Литература:

  • Исходный код/​​документация: GitHub

PHPASS

Это слой, который поддерживает bcrypt, но также поддерживает довольно сильный алгоритм, который полезен, если у вас нет доступа к PHP >= 5.3.2... Он фактически поддерживает PHP 3.0+ (хотя и не с 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
    }
}

Ресурсы

Примечание.. Не используйте альтернативы PHPASS, которые не размещены в openwall, это разные проекты.

О BCrypt

Если вы заметили, каждая из этих библиотек вернет одну строку. Это из-за того, как BCrypt работает внутри. И есть TON ответов об этом. Вот выбор, который я написал, что я не буду копировать/вставлять сюда, но ссылаюсь на:

Обернуть

Есть много разных вариантов. Который вы выбираете, зависит от вас. Тем не менее, я бы ВЫСОКО рекомендовал использовать одну из вышеупомянутых библиотек для обработки этого для вас.

Опять же, если вы используете crypt() напрямую, вы, вероятно, делаете что-то неправильно. Если ваш код использует hash() (или md5() или sha1()) напрямую, вы почти наверняка делаете что-то неправильно.

Просто используйте библиотеку...

  • 6
    Соль должна генерироваться случайным образом, однако она не должна поступать из безопасного случайного источника. Соль не секрет . Возможность угадать следующую соль не оказывает реального влияния на безопасность; пока они поступают из достаточно большого пула данных, чтобы генерировать различные соли для каждого закодированного пароля, у вас все в порядке. Помните, что соль предназначена для предотвращения использования радужных таблиц, если ваши хеши попадают в плохие руки. Они не секрет.
  • 7
    @AndrewMoore абсолютно правильно! Однако соль должна обладать достаточной энтропией, чтобы быть статистически уникальной. Не только в вашем приложении, но и во всех приложениях. Таким образом, mt_rand() имеет достаточно высокий период, но начальное значение составляет всего 32 бита. Таким образом, использование mt_rand() эффективно ограничивает вас только 32 битами энтропии. Который благодаря проблеме дня рождения означает, что у вас есть 50% -й шанс столкновения только на 7 000 сгенерированных солей (глобально). Так как bcrypt принимает 128 битов соли, лучше использовать источник, который может предоставить все 128 битов ;-). (при 128 битах вероятность столкновения составляет 50% при хеше 2e19) ...
Показать ещё 4 комментария
41

Вы получите много информации в Достаточно с таблицами Rainbow: что вам нужно знать о безопасных схемах паролей или Переносимая хеширование PHP-паролей.

Цель состоит в том, чтобы хешировать пароль с чем-то медленным, поэтому кто-то, получающий вашу базу данных паролей, умрет, пытаясь переборщить его (10 ms delay для проверки пароля ничего для вас, много для кого-то, пытающегося перебрать силу Это). Bcrypt медленный и может использоваться с параметром, чтобы выбрать, насколько он медленный.

  • 0
    @Arkh: Замедление того, как быстро он работает, не проблема. Брутфорсинг будет работать только тогда, когда вы, будучи разработчиком, не смогли применить политики надежных паролей.
  • 7
    Примените все, что вы хотите, пользователям удастся испортить и использовать один и тот же пароль на нескольких вещах. Таким образом, вы должны максимально защитить его или реализовать что-то, что позволит вам не хранить пароль (SSO, openID и т. Д.).
Показать ещё 8 комментариев
33

Вы можете создать односторонний хеш с помощью bcrypt с помощью функции PHP crypt() и передать соответствующую соль Blowfish. Самое важное из всего уравнения состоит в том, что A) алгоритм не был скомпрометирован, а B) вы правильно соляли каждый пароль. Не используйте соль для всей заявки; который открывает все ваше приложение для атаки из одного набора таблиц Rainbow.

PHP - функция склепа

  • 4
    Это правильный подход - используйте функцию crypt() PHP, которая поддерживает несколько различных функций хеширования паролей. Убедитесь, что вы не используете CRYPT_STD_DES или CRYPT_EXT_DES - CRYPT_EXT_DES любой из других поддерживаемых типов (включая bcrypt под именем CRYPT_BLOWFISH ).
  • 4
    У SHA действительно есть и параметр стоимости, с помощью опции «раундов». Когда я использую это, я также не вижу причин отдавать предпочтение bcrypt.
Показать ещё 3 комментария
31

Изменить: 2013.01.15. Если ваш сервер будет поддерживать его, используйте решение martinstoeckli.


Каждый хочет сделать это более сложным, чем есть. Функция crypt() выполняет большую часть работы.

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

Пример:

$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

Я знаю, что это должно быть очевидно, но, пожалуйста, не используйте "пароль" в качестве пароля.

  • 3
    Создание соли может быть улучшено (используйте случайный источник ОС), в противном случае это выглядит хорошо для меня. Для более новых версий PHP лучше использовать 2y вместо 2a .
  • 0
    используйте mcrypt_create_iv($size, MCRYPT_DEV_URANDOM) качестве источника соли.
Показать ещё 10 комментариев
23

Версия 5.5 PHP будет иметь встроенную поддержку BCrypt, функции password_hash() и password_verify(). На самом деле это всего лишь обертки вокруг функции crypt() и упростить ее использование. Он заботится о создании безопасной случайной соли и обеспечивает хорошие значения по умолчанию.

Самый простой способ использования этих функций:

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

Этот код будет хэш-пароль с помощью BCrypt (алгоритм 2y), генерирует случайную соль из случайного источника ОС и использует параметр стоимости по умолчанию (на данный момент это 10). Вторая строка проверяет, совпадает ли введенный пользователем пароль с уже сохраненным хэш-значением.

Если вы хотите изменить параметр стоимости, вы можете сделать это так, увеличивая параметр стоимости на 1, удваивая необходимое время для вычисления значения хэш-функции:

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

В отличие от параметра "cost", лучше опустить параметр "salt", потому что функция уже делает все возможное, чтобы создать криптографически безопасную соль.

Для PHP версии 5.3.7 и более поздних версий существует пакет совместимости от того же автора, который сделал функцию password_hash(). Для версий PHP до 5.3.7 не поддерживается crypt() с 2y, безопасным алгоритмом BCrypt в Unicode. Вместо него можно заменить 2a, что является лучшей альтернативой для ранних версий PHP.

  • 3
    После того, как я прочитал это, моей первой мыслью было «как вы храните полученную соль»? После просмотра документов функция password_hash () в итоге генерирует строку, в которой хранятся метод шифрования, соль и сгенерированный хэш. Таким образом, он просто хранит все, что нужно, в одной строке для работы функции password_verify (). Просто хотел упомянуть об этом, так как это может помочь другим, когда они видят это.
  • 0
    @ jzimmerman2011 - Точно, в другом ответе я попытался объяснить этот формат хранения на примере.
5

Альтернативой является использование scrypt, специально разработанного для того, чтобы превосходить bcrypt Колина Персиваля в расшифровывать расширение PHP в PECL. В идеале этот алгоритм будет переведен в PHP, чтобы его можно было указать для функций password_ * (в идеале, как" PASSWORD_SCRYPT"), но этого еще нет.

4

Текущее мышление: хеши должны быть самыми медленными, а не самыми быстрыми. Это подавляет атаки радуги.

Также связано, но предосторожно: злоумышленник никогда не должен иметь неограниченный доступ к вашему экрану входа в систему. Чтобы предотвратить это: настройте таблицу отслеживания IP-адреса, которая записывает каждое нажатие вместе с URI. Если более 5 попыток входа в систему поступают с одного и того же IP-адреса в любой пятиминутный период, заблокируйте с объяснением. Вторичный подход состоит в том, чтобы иметь двухуровневую схему паролей, например, банки. Помещение блокировки для сбоев на втором проходе повышает безопасность.

Резюме: замедлить атакующего, используя длительные хэш-функции. Кроме того, заблокируйте слишком много доступа к вашему логину и добавьте второй уровень пароля.

  • 0
    Я думаю, они предполагают, что злоумышленнику уже удалось украсть мою БД с помощью других средств, и теперь пытается получить пароли, чтобы опробовать их на PayPal или что-то в этом роде.
  • 4
    На полпути до 2012 года, и этот ответ все еще сомнительный, как алгоритм медленного хеширования предотвращает атаки радужного стола? Я думал, что случайная соль диапазона байтов сделала? Я всегда думал, что скорость алгоритма хеширования определяет, сколько итераций они могут отправить против хэша, который они получили от вас за определенное время. Кроме того, НИКОГДА НЕ ЗАБИРАЙТЕ ПОЛЬЗОВАТЕЛЯ НА НЕУДАЧИХ ПОПЫТКАХ ВХОДА, поверьте мне, ваши пользователи будут сыты по горло, часто на некоторых сайтах мне нужно входить в систему почти 5 раз, иногда больше, прежде чем я запомнил свой пароль. Также второй уровень прохода не работает, хотя двухэтапная аутентификация с кодом мобильного телефона могла бы.
Показать ещё 3 комментария
2

Для паролей OAuth 2:

$bcrypt = new \Zend\Crypt\Password\Bcrypt;
$bcrypt->create("youpasswordhere", 10)
Сообщество Overcoder
Наверх
Меню