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

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

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

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

Ответ 1

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.

Ответ 2

Итак, вы хотите использовать 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()) напрямую, вы почти наверняка делаете что-то неправильно.

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

Ответ 3

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

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

Ответ 4

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

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

Ответ 5


Изменить: 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

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

Ответ 6

Версия 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.

Ответ 7

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

Ответ 8

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

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

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

Ответ 9

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

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