Безопасность всегда была неоднозначной темой, провоцирующей многочисленные горячие споры. И всё благодаря обилию самых разных точек зрения и «идеальных решений», которые устраивают одних и совершенно не подходят другим. Я считаю, что взлом системы безопасности приложения всего лишь вопрос времени. Из-за быстрого роста вычислительных мощностей и увеличения сложности безопасные сегодня приложения перестанут завтра быть таковыми.

Прим. перев.: для более полной картины здесь вас также будет ждать перевод Hashing Passwords with the PHP 5.5 Password Hashing API, на которую автор ссылается в статье.

Если вы не изучали алгоритмы хэширования, то, вероятнее всего, воспринимаете их как одностороннюю функцию, которая конвертирует данные переменной длины в данные фиксированной длины. Проанализируем это определение:
  • Односторонняя функция: из хэша невозможно восстановить исходные данные с помощью какого-либо эффективного алгоритма.
  • Конвертация данных переменной длины в данные фиксированной длины: входное значение может быть «бесконечной» длины, а выходное — нет. Это подразумевает, что два или более входных значения могут иметь одинаковые хэши. Чем меньше длина хэша, тем выше вероятность коллизии.

Алгоритмы MD5 и SHA-1 уже не обеспечивают достаточно высокой надёжности с точки зрения вероятности возникновения коллизий (см. Парадокс дней рождения). Поэтому рекомендуется использовать алгоритмы, генерирующие более длинные хэши (SHA-256, SHA-512, whirlpool и др.), что делает вероятность возникновения коллизии пренебрежимо малой. Такие алгоритмы ещё называют «псевдослучайными функциями», т. е. результаты их работы неотличимы от результатов работы полноценного генератора случайных чисел (true random number generator, TRNG).

Недостатки простого хэширования


Тот факт, что с помощью эффективного алгоритма невозможно провести операцию, обратную хэшированию, и восстановить исходные данные, не означает, что вас не могут взломать. Если хорошо поискать, то можно найти базы данных с хэшами распространённых слов и коротких фраз. Кроме того, простые пароли можно быстро и легко брутфорсить или взламывать перебором по словарю.

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



Злоумышленники могут поступить ещё проще — нагуглить конкретные хэши в онлайновых БД:

Также нужно понимать, что если два и более одинаковых пароля имеют одинаковые хэши, то, взломав один хэш, мы получаем доступ ко всем аккаунтам, где используется тот же пароль. Для примера: пусть у нас несколько тысяч пользователей, наверняка несколько из них используют пароль 123456 (если настройки сайта не заставляют усложнять пароль). MD5-хэш для этого пароля e10adc3949ba59abbe56e057f20f883e. Так что если вы заполучите этот хэш и поищете в базе данных по этому значению, то найдёте всех пользователей с таким паролем.

Почему небезопасны хэши с применением соли


Чтобы затруднить атаки описанного вида, применяется так называемая соль. Это стандартное средство, но в условиях современных вычислительных мощностей его уже недостаточно, особенно если длина соли невелика.

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

f(password, salt) = hash(password + salt)

Для затруднения брутфорс-атаки соль должна быть длиной не менее 64 символов. Но проблема в том, что для дальнейшей аутентификации пользователей соль должна храниться в БД в виде простого текста.

if (hash([введённый пароль] + [соль]) == [хэш]) тогда пользователь аутентифицирован

Благодаря уникальности соли для каждого пользователя мы можем решить проблему коллизий простых хэшей. Теперь все хэши будут разными. Также уже не сработают подходы с гугленьем хэшей и брутфорсом. Но если злоумышленник через внедрение SQL-кода получит доступ к соли или БД, то сможет успешно атаковать брутфорсом или перебором по словарю, особенно если пользователи выбирают распространённые пароли (а-ля 123456).

Тем не менее взлом любого из паролей уже не позволит автоматически вычислить пользователей, у которых тот же пароль, — ведь у нас ВСЕ хэши разные.

Момент случайности


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

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

Когда от компьютера хотят случайное число, то обычно он берёт данные из нескольких источников (например, переменные среды: дату, время, количество записанных/считанных байтов и т. д.), а затем производит над ними вычисления для получения «случайных» данных. Поэтому такие данные называют псевдослучайными. А значит, если каким-то образом воссоздать набор исходных состояний на момент исполнения псевдослучайной функции, то мы сможем сгенерировать то же самое число.

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

image

А теперь сравните с данными, сгенерированными полноценным генератором случайных чисел:



К сожалению, ни rand(), ни mt_rand() нельзя считать подходящими инструментами для обеспечения высокого уровня безопасности.

Если вам нужно получить случайные данные, воспользуйтесь функцией openssl_random_pseudo_bytes(), которая доступна начиная с версии 5.3.0. У неё даже есть флаг crypto_strong, который сообщит о достаточном уровне безопасности.

Пример использования:
<?php

function getRandomBytes ($byteLength)
{
    /*
     * Проверка доступности openssl_random_pseudo_bytes
     */
    if (function_exists('openssl_random_pseudo_bytes')) {
        $randomBytes = openssl_random_pseudo_bytes($byteLength, $cryptoStrong);
        if ($cryptoStrong)
            return $randomBytes;
    } 

    /*
     * Если openssl_random_pseudo_bytes недоступен или результат его работы слишком
     * слабый, то задействуется менее безопасный генератор
     */
    $hash = '';
    $randomBytes = '';

    /*
     * На Linux/UNIX-системах /dev/urandom является прекрасным источником энтропии, 
     * используйте его для получения начального значения $hash
     */
    if (file_exists('/dev/urandom')) {
        $fp = fopen('/dev/urandom', 'rb');
        if ($fp) {
            if (function_exists('stream_set_read_buffer')) {
                stream_set_read_buffer($fp, 0);
            }
            $hash = fread($fp, $byteLength);
            fclose($fp);
        }
    }

    /*
     * Используйте менее безопасную функцию mt_rand(), но только не rand()!
     */
    for ($i = 0; $i < $byteLength; $i ++) {
        $hash = hash('sha256', $hash . mt_rand());
        $char = mt_rand(0, 62);
        $randomBytes .= chr(hexdec($hash[$char] . $hash[$char + 1]));
    }
    return $randomBytes;
}

Растяжение пароля


Можно внедрить растяжение пароля, это позволяет ещё больше затруднить брутфорс-атаки. Растяжение представляет собой итеративный, или рекурсивный, алгоритм, который раз за разом вычисляет хэш самого себя, десятки тысяч раз (а то и более).



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

Для взлома пароля с растяжением нужно:
  1. знать точное количество итераций, поскольку любое отклонение будет давать другой хэш;
  2. ждать не менее секунды между каждой попыткой.

Это делает атаку очень маловероятной… но не невозможной. Чтобы преодолеть секундную задержку, атакующий должен использовать более производительный компьютер, чем тот, под который был настроен алгоритм хэширования. Следовательно, процесс взлома может потребовать дополнительных расходов.

Для растяжения пароля можно использовать стандартные алгоритмы, например PBDKDF2, представляющий собой функцию формирования ключа:
<?php

/*
 * Количество итераций можно увеличить, чтобы перекрыть дальнейший рост 
 * производительности CPU/GPU. Используйте разные соли для каждого пароля
 * (можно класть их вместе со сгенерированным паролем). Данная функция
 * работает медленно, это сделано преднамеренно! Больше информации здесь: -
 * http://ru.wikipedia.org/wiki/PBKDF2 - http://www.ietf.org/rfc/rfc2898.txt
 */
function pbkdf2 ($password, $salt, $rounds = 15000, $keyLength = 32, 
        $hashAlgorithm = 'sha256', $start = 0)
{
    // Key blocks to compute
    $keyBlocks = $start + $keyLength;

    // Derived key
    $derivedKey = '';

    // Create key
    for ($block = 1; $block <= $keyBlocks; $block ++) {
        // Initial hash for this block
        $iteratedBlock = $hash = hash_hmac($hashAlgorithm, 
                $salt . pack('N', $block), $password, true);

        // Perform block iterations
        for ($i = 1; $i < $rounds; $i ++) {
            // XOR each iteration
            $iteratedBlock ^= ($hash = hash_hmac($hashAlgorithm, $hash, 
                    $password, true));
        }

        // Append iterated block
        $derivedKey .= $iteratedBlock;
    }

    // Return derived key of correct length
    return base64_encode(substr($derivedKey, $start, $keyLength));
}

Есть и более затратные по времени и памяти алгоритмы, например bcrypt (о нём мы поговорим ниже) или scrypt:
<?php
// bcrypt внедрён в функцию crypt()
$hash = crypt($pasword, '$2a$' . $cost . '$' . $salt);

  • $cost — коэффициент трудоёмкости;
  • $salt — случайная строка. Её можно генерировать, например, с помощью описанной выше функции secure_rand().

Коэффициент трудоёмкости целиком зависит от машины, на которой выполняется хэширование. Можете начать со значения 09 и постепенно увеличивать, пока длительность операции не достигнет одной секунды. Начиная с версии 5.5 можно пользоваться функцией password_hash(), об этом мы поговорим дальше.

На данный момент в PHP не реализована поддержка алгоритма scrypt, но можно воспользоваться реализацией от Domblack.

Применение технологий шифрования


Многие путаются в терминах «хэширование» и «шифрование». Как было упомянуто выше, хэш — результат работы псевдослучайной функции, в то время как шифрование — осуществление псевдослучайного преобразования: входные данные делятся на части и обрабатываются таким образом, что результат становится неотличим от результата работы полноценного генератора случайных чисел. Однако в этом случае можно провести обратное преобразование и восстановить исходные данные. Преобразование осуществляется с помощью криптоключа, без которого невозможно провести обратное преобразование.

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

Необходимо уделять большое внимание правильности использования шифрования. Не думайте, что для защиты важных данных достаточно просто зашифровать по какому-нибудь алгоритму. Есть немало способов украсть данные. Главное правило — никогда не занимайтесь самодеятельностью и пользуйтесь уже готовыми, отработанными реализациями.

Некоторое время назад у Adobe была мощная утечка пользовательской БД из-за неправильно реализованного шифрования. Давайте разберём, что у них произошло.

image

Предположим, что в таблице хранятся следующие данные в виде обычного текста:



Кто-то в Adobe решил зашифровать пароли, но при этом совершил две большие ошибки:
  1. использовал один и тот же криптоключ;
  2. оставил поля passwordHint незашифрованными.

Допустим, после шифрования таблица стала выглядеть так:

image

Мы не знаем, какой применялся криптоключ. Но если проанализировать данные, то можно заметить, что в строках 2 и 7 используется один и тот же пароль, так же как и в строках 3 и 6.

Пришло время обратиться к подсказке пароля. В строке 6 это «I’m one!», что совершенно неинформативно. Зато благодаря строке 3 мы можем предположить, что пароль — queen. Строки 2 и 7 по отдельности не позволяют вычислить пароль, но если проанализировать их вместе, то можно предположить, что это halloween.

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



Пусть у нас есть тысячи пользователей и мы хотим зашифровать все пароли. Как было показано выше, лучше избегать использования единого криптоключа. Но и сделать для каждого пользователя уникальный ключ мы тоже не можем, поскольку хранение ключей само по себе превратится в проблему. В этом случае достаточно применять общий для всех криптоключ, но при этом делать «настройку», уникальную для каждого пользователя. Комбинация ключа и «настройки» и будет являться уникальным ключом для каждого пользователя.

Простейший вариант «настройки» — так называемый первичный ключ, уникальный для каждой записи в таблице. Не рекомендуется пользоваться им в жизни, здесь он показан лишь для примера:

f(key, primaryKey) = key + primaryKey

Здесь ключ и первичный ключ просто сцепляются вместе. Но для обеспечения безопасности следует применить к ним алгоритм хэширования или функцию формирования ключа (key derivation function). Также вместо первичного ключа можно для каждой записи использовать одноразовый ключ (аналог соли).

Если мы применим к нашей таблице настраиваемое шифрование, то она будет выглядеть так:

image

Конечно, нужно будет ещё что-то сделать с подсказками паролей, но всё-таки уже получилось хоть что-то адекватное.

Обратите внимание, что шифрование — не идеальное решение для хранения паролей. В связи с угрозами внедрения кода лучше избегать этого метода защиты. Для хранения паролей надежнее всего использовать алгоритм bcrypt. Но нельзя забывать и о том, что даже самые лучшие и проверенные решения обладают уязвимостями.

PHP 5.5


Сегодня оптимальным способом хэширования паролей считается использование bcrypt. Но многие разработчики всё ещё предпочитают старые и более слабые алгоритмы вроде MD5 и SHA-1. А некоторые при хэшировании даже не пользуются солью. В PHP 5.5 был представлен новый API для хэширования, который не только поощряет применение bcrypt, но и существенно облегчает работу с ним. Давайте разберём основы использования этого нового API.

Здесь применяются четыре простые функции:
  • password_hash() — хэширование пароля;
  • password_verify() — сравнение пароля с хэшем;
  • password_needs_rehash() — перехэширование пароля;
  • password_get_info() — возвращение названия алгоритма хэширования и применявшихся в ходе хэширования опций.

password_hash()


Несмотря на высокий уровень безопасности, обеспечиваемый функцией crypt(), многие считают её слишком сложной, из-за чего программисты часто допускают ошибки. Вместо неё некоторые разработчики используют для генерирования хэшей комбинации слабых алгоритмов и слабых солей:
<?php
$hash = md5($password . $salt); // работает, но уровень безопасности невысок

Функция password_hash() существенно облегчает разработчику жизнь и повышает безопасность кода. Для хэширования пароля достаточно скормить его функции, и она вернёт хэш, который можно поместить в БД:
<?php
$hash = password_hash($passwod, PASSWORD_DEFAULT);

И всё! Первый аргумент — пароль в виде строки, второй аргумент задаёт алгоритм генерирования хэша. По умолчанию используется bcrypt, но при необходимости можно добавить и более сильный алгоритм, который позволит генерировать строки большей длины. Если в своём проекте вы используете PASSWORD_DEFAULT, то удостоверьтесь, что ширина колонки для хранения хэшей не менее 60 символов. Лучше сразу задать 255 знаков. В качестве второго аргумента можно использовать PASSWORD_BCRYPT. В этом случае хэш всегда будет длиной в 60 символов.

Обратите внимание, что вам не нужно задавать значение соли или стоимостного параметра. Новый API всё сделает за вас. Поскольку соль является частью хэша, то вам не придётся хранить её отдельно. Если вам всё-таки нужно задать своё значение соли (или стоимости), то это можно сделать с помощью третьего аргумента:
<?php
$options = [
    'salt' => custom_function_for_salt(), // Напишите собственный код генерирования соли
    'cost' => 12 // По умолчанию стоимость равна 10
];
$hash = password_hash($password, PASSWORD_DEFAULT, $options);

Всё это позволит вам использовать самые свежие средства обеспечения безопасности. Если в дальнейшем в PHP появится более сильный алгоритм хэширования, то ваш код станет использовать его автоматически.

password_verify()


Теперь рассмотрим функцию сравнения пароля с хэшем. Первый вводится пользователем, а второй мы берём из БД. Пароль и хэш используются в качестве двух аргументов функции password_verify(). Если хэш соответствует паролю, то функция возвращает true.
<?php
if (password_verify($password, $hash)) {
    // Успешно!
}
else {
    // Неверные данные
}

Помните, что соль является частью хэша, поэтому она не задаётся здесь отдельно.

password_needs_rehash()


Если вы захотите повысить уровень безопасности, добавив более сильную соль или увеличив стоимостный параметр, или изменится алгоритм хэширования по умолчанию, то вы, вероятно, захотите перехэшировать все имеющиеся пароли. Данная функция поможет проверить каждый хэш на предмет того, какой алгоритм и параметры использовались при его создании:
<?php
if (password_needs_rehash($hash, PASSWORD_DEFAULT, ['cost' => 12])) {
    // Пароль надо перехэшировать, поскольку использовался не текущий
    // алгоритм по умолчанию либо стоимостный параметр не был равен 12
    $hash = password_hash($password, PASSWORD_DEFAULT, ['cost' => 12]);

    // Не забудьте сохранить новый хэш!
}

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

password_get_info()


Данная функция берёт хэш и возвращает ассоциативный массив из трёх элементов:
  • algo — константа, позволяющая идентифицировать алгоритм;
  • algoName — название использовавшегося алгоритма;
  • options — значения разных опций, применявшихся при хэшировании.

Более ранние версии PHP


Как видите, работать с новым API не в пример легче, чем с неуклюжей функцией crypt(). Если же вы используете более ранние версии PHP, то рекомендую обратить внимание на библиотеку password_compact. Она эмулирует данный API и автоматически отключается, когда вы обновляетесь до версии 5.5.

Заключение


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

Комментарии (42)


  1. lostpassword
    19.11.2015 18:39

    Спасибо за статью! Поясните, пожалуйста, один момент касательно функции password_hash().

    Обратите внимание, что вам не нужно задавать значение соли или стоимостного параметра. Новый API всё сделает за вас.
    А откуда password_hash() возьмет соль и где будет ее хранить?


    1. AloneCoder
      19.11.2015 18:42

      А откуда password_hash() возьмет соль

      Он ее сгенерирует
      и где будет ее хранить?
      Поскольку соль является частью хэша, то вам не придётся хранить её отдельно


      1. lostpassword
        19.11.2015 18:50

        Теперь въехал, спасибо!)


  1. Revertis
    19.11.2015 18:40

    Уже не первый раз читаю о том, что нельзя хранить просто хэши от пароля, чтобы они не были одинаковыми, что надо добавлять разную соль, да еще и хранить её рядом в базе. Это всё понятно, но почему нельзя к хэшированию добавить логин юзера, который обычно никогда не меняется, и какую-то общую соль? Её вообще можно хранить где-то отдельно. Пожалуйста, только не в карму!


    1. zein
      19.11.2015 18:55

      а зачем? а менять логины нельзя? есть ведь blowfish, зачем все эти велосипеды?


      1. Revertis
        19.11.2015 19:03

        Я же написал "который обычно никогда не меняется". А вы когда последний раз видели, чтобы логин у пользователя менялся? А если учесть, что логином обычно выступает почтовый адрес, то почти никогда. А если и давать юзеру менять почту-логин, то одновременно можно спросить для подтверждения текущий пароль — а с ним и перехэшировать и сохранить новый хэш.


        1. shoomyst
          19.11.2015 20:19

          Ну так проще вообще User ID использовать, он не меняется вовсе. Никто ж не запрещает. Тот же linkedin хранил пароли вообще черт-те как, до слива. Так что вряд ли вас упрекнут.
          Но в то же время проще/надежнее прислушиваться к советам и мнениям специалистов в той или иной области, ибо:

          Для затруднения брутфорс-атаки соль должна быть длиной не менее 64 символов.
          и еще куча различных нюансов, о которых можно знать только если серьезно погружен в данную область


          1. Revertis
            19.11.2015 21:53

            Вы невнимательны. Я предлагал одну большую общую соль, плюс логин (для разных результирующих хэшей), и плюс уже сам пароль. Тогда на глаз найти одинаковые пароли нельзя. А если соль не хранится там же в базе, а утекла только база, вы фиг разгадаете пароли.


            1. Delphinum
              19.11.2015 21:58

              В качестве соли можете использовать все что вам нравится. Хотите логин хэшируйте и используйте результат в качестве соли, хотите логин + общую соль. Если вас смущает хранение соли в базе, храните соль в файлах, NoSQL базе или в облаке, тоже вариантов куча.


              1. Revertis
                19.11.2015 22:09

                Это я плохо написал, или вы все невнимательны?
                Я предлагал хэшировать одну общую соль, плюс разные логины, плюс пароль. (hash($pass. $login. $mega_salt))
                Общая соль достаточно большая, хранящаяся не в базе. Сервер, естественно, защищен.
                Получается, что если утечёт база, как обычно и бывает (в новостях), то хакеры не смогут разгадать пароли или найти одинаковые.


                1. Delphinum
                  19.11.2015 22:26

                  Я вас прекрасно понял. Повторю еще раз: вы можете использовать в качестве соли все что угодно, логин, логин + соль, логин + соль + мега-соль и т.д. Хранить соль вы так же можете где угодно. Все это лишь частные случае одного решения: hash(пароль + соль).


        1. nazarpc
          23.11.2015 06:25

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


          1. Revertis
            23.11.2015 12:21

            Подождите, при добавлении хотя бы одного нового символа хэш меняется весь. А соль применяется для того, чтобы сделать все хэши разными. Эта задача идеально решается с помощью подмешивания туда логина.


            1. Delphinum
              23.11.2015 16:52

              Ну не совсем идеально. Обычно соль это закрытые данные, некий случайный набор символов, а логин открыт. Теоретически можно даже догадаться, что в качестве соли используется логин (если логин не «вмешивается» каким то причудливым образом).


              1. Revertis
                23.11.2015 17:09

                Ну почитайте другие мои комменты. При подмешивании логина соль может быть одинаковой, довольно большой. И как раз, храниться не в базе, а в другом месте. Если вдруг через инъекцию спалят всю базу, соль не утечёт.


                1. Delphinum
                  23.11.2015 17:41

                  Да я читал эти ваши комментарии. Можно вовсе не использовать общую соль, заменив ее на какие то операции над логином, делающие его более случайным.


    1. lostpassword
      19.11.2015 19:03

      Я не специалист, но попробую высказать свои мысли.)

      добавить логин юзера
      Логины обычно довольно простые и часто повторяются (в смысле на разных сайтах люди используют один логин). Поэтому использовать логин в качестве соли вряд ли целесообразно — ее легко узнать, легко подобрать.
      С другой стороны, использовать логин вместе с солью, по большому счету, бессмысленно (если соль хороша, то прибавление к ней логина мало что изменит)
      хранить соль где-то отдельно
      Это, ИМХО, нереализуемо в принципе: чтобы иметь возможность аутентифицировать пользователя, веб-приложение должно иметь возможность эту соль считать. При этом неважно, где она будет храниться: в БД, в файле на диске, на внешнем FTP-сервере — в любом случае при компрометации web-сервера злоумышленник получит доступ к этой соли.
      Единственный повод хранить соль не в БД, а где-то еще — это некоторое снижение рисков утечки паролей в том случае, если злоумышленник получит только доступ к БД (например, через SQL-инъекцию), но не сможет получить доступ к файлам и шеллу. Но стоит ли игра свеч — сказать сложно.


      1. Revertis
        19.11.2015 19:13

        прибавление к ней логина мало что изменит
        Тут вы неправы — весь хэш станет совершенно другим.
        веб-приложение должно иметь возможность эту соль считать
        Делаем отдельный сервер, который содержит соль и отдает только серверу с веб-приложением (по IP проверяет, например). Эту соль забирает не веб-приложение, а некий кэш в памяти (демон), а уже приложение получает его у демона и все рады. Получается, что даже физический доступ к серверу с выключением и переносом в другое место (с другим IP) не дает злоумышленникам или госструктурам доступа к паролям.


        1. lostpassword
          19.11.2015 19:21

          Тут вы неправы
          Извините, не соглашусь.)
          Чем соль «1937569admin» лучше «193756984938»? Добавление имени пользователя только лишь уменьшает длину той «эффективной» части соли, которая формируется случайно. А если мы в любом случае генерируем соль, то зачем нам усложнять алгоритм и дописывать к ней логин пользователя, когда проще (и правильнее) саму соль увеличить на несколько байт?
          Делаем отдельный сервер
          ОК, сделали.)
          Злоумышленник находит уязвимость, заливает на веб-сервер шелл, заходит под привелегированной учетной записью… После чего внимательно изучает, с какого там внешнего сервера приходит соль и к какому порту нужно подключаться, чтобы ее получить.
          Другой вопрос уже, что многие злоумышленники могут посмотреть на эту навороченную конструкцию и просто слиться, ибо не хочется это все разматывать. Но если хакер решит заняться делом всерьез — внешний сервер ничем не поможет.


          1. Revertis
            19.11.2015 21:47

            лишь уменьшает длину той «эффективной» части соли
            Это вы с чего взяли? Я не могу добавить к хэшированию user@mail.com и половину какого-нибудь рассказа, что ли? Запросто! И она не нужна разная для всех юзеров тогда, потому, что если вспомнить, для чего предлагают использовать соль — сделать все хэши разными. А эту задачу у нас выполняют логины.
            Злоумышленник находит уязвимость, заливает на веб-сервер шелл, заходит под привелегированной учетной записью…
            В таком случае ничего не поможет. Вообще. Мы не эту тему обсуждаем. А тему хранения разных солей отдельной колонкой в той же базе юзеров.


            1. lostpassword
              19.11.2015 22:03

              А чем 100 байт соли и логин лучше 120 байт или 150 байт чистой соли? Экономия на одном столбце таблицы БД?


              1. Revertis
                19.11.2015 22:14

                Не требуется генерировать каждому свою соль. Соли не утекают злоумышленнику вместе с базой.


                1. lostpassword
                  19.11.2015 22:31

                  Первое, ИМХО, несерьезно: генерация соли почти не требует ресурсов.
                  Со вторым согласен, но для этого нет никакой необходимости использовать логины: достаточно хранить соль отдельно от БД.


    1. KIVagant
      19.11.2015 21:49

      Потому что для того, чтобы узнать «соль» в таком случае даже не нужно будет лезть в базу. А так да, вполне можно.


      1. Revertis
        19.11.2015 21:59

        Вы утверждаете, что база намного лучше защищена, чем доступ по ssh, например? Почему же именно базы и утекают? Тогда можно и плейном хранить, если главная дверь не заперта.


        1. KIVagant
          19.11.2015 22:39

          Базы часто утекают как раз из-за получения доступа к самому серверу через уязвимости различного ПО. А получив доступ к файловой системе не составляет труда прочитать конфиги приложений с паролями доступа к БД. Но это всё абстрактно, есть доступная аналитика некоторых исследователей по разным видам уязвимостей и их популярности.


  1. Revertis
    19.11.2015 20:22

    Промахнулся


  1. CodeRush
    20.11.2015 09:32

    Зря автор не упомянул конкурс P-H-C и его победителя — семейство алгоритмов Argon2.


  1. betal
    20.11.2015 11:21

    Возникает много вопросов:

    Растяжение представляет собой итеративный, или рекурсивный, алгоритм, который раз за разом вычисляет хэш самого себя, десятки тысяч раз (а то и более).

    На хабре проводилось исследование. Это приведет к увеличению вероятности коллизии и упрощению брутфорса (по радужным таблицам).
    Для генерирования подходящей соли нам нужен хороший генератор случайных чисел. Сразу забудьте о функции rand().

    Не понятно чем это обосновано. Соль нужна чтобы по радужным таблицам не найти хэш. В редких случаях когда соль не доступна, при брутфорсе может быть и имеет смысл на жизнь то что вы говорите. Но при этом надо понимать, что стоимость исследований эффективности различных методов генератора случайных чисел затребует достаточно много человекочасов и скорее всего станет нерентабельной. Так же, хотелось бы увидеть код, которым вы сгенерировали картинку для rand(), так как рисунок больше похож на ошибку в программе, не может каждое 30 значение функции rand быть 100% предсказуемым в общем случае.

    Так же если вы пишете о том, что нужно добавлять соль, обязательно надо указывать о порядке следования соли и пароля в хэширующей функции для неслучайной соли. Ибо если этот порядок неверный, от соли не будет никакого толку.
    Для затруднения брутфорс-атаки соль должна быть длиной не менее 64 символов.

    Тоже не понятно, откуда эта цифра и для какого алгоритма? Почему не 16 или почему не 256? Опять же каких символов? (a-z или 0x00-0xFF),
    На примере того же md5 в радужных таблицах сейчас редко запись для пароля длиннее 10-12 символов. (псевдослучайных)

    ждать не менее секунды между каждой попыткой.

    тоже сомнительный совет, откуда эта цифра?
    Я как пользователь, если буду ждать секунду пока мой пароль будет проверяться, успею три раза уйти. Почему не 100 мс? Это уже будет не сотни миллионов паролей в секунду для перебора, а всего 10 в секунду.


    1. betal
      20.11.2015 11:40

      знать точное количество итераций, поскольку любое отклонение будет давать другой хэш;

      Слишком просто вычислить.
      Достаточно знать свой пароль.


    1. betal
      20.11.2015 12:05

      Алгоритмы MD5 и SHA-1 уже не обеспечивают достаточно высокой надёжности с точки зрения вероятности возникновения коллизий (см. Парадокс дней рождения).

      Опять же зависит от стоимости информации. Я не отрицаю что в SHA-256 вероятность коллизии меньше чем в SHA-1, но и в MD5 и SHA-1 она крайне мала.
      в парадоксе дня рождения всего 365 вариантов значения и 23 человека (6%)
      в MD5 вариантов уже 3,4 ? 10^38. т.е. даже если все 7 миллиардов человек зарегистрируются, вероятность коллизии будет очень мала и по большому счету ничего не даст. Даже в радужных таблицах вероятность коллизии будет на мой взгляд очень маленькой.
      Для примера у меня 80 000 000 записей MD5, больше 4 байт коллизия бывает крайне редко.


    1. betal
      20.11.2015 12:28

      Например, наш арсенал средств защиты относительно недавно пополнился так называемыми функциями губки.

      Минус хэшей произвольной длины в том, что мы знаем у каких пользователей пароль короткий, а у каких длинный. И зная длину пароля, можно сразу начать брутфорсить только администратора с самым коротким паролем.


  1. Indexator
    23.11.2015 10:20

    Всегда считал, что подсказки пароля (или вообще использование ответа на подсказку вместо пароля) серьезной дырой в безопасности и терпеть ненавижу, когда какие-либо системы или сервисы предлагают и даже требуют заполнения этого поля, отказываясь продолжать работу! ИЧСХ, таких систем и сервисов до сих пор over 9000, даже среди вполне серьезных компаний!

    Это же надо было кому-то когда-то додуматься до такой идиотской мысли, а другим ее повторить! А чего сразу тогда не выводить, как в «Кто хочет стать миллионером?», 4 варианта ответов и 3 опции: звонок админу, помощь суппорта и 50/50?! Можно пойти еще дальше — задавать уточняющие и наводящие вопросы… Не, ну а чо?

    Считаю, что те, кто применяет механизм подсказок, а особенно тот, кто придумал эту идею, достойны всеобщего презрения и порицания, за натуральную диверсию и подрывную деятельность в вопросах информационной безопасности!


    1. shoomyst
      23.11.2015 13:43

      Надо было просить вводить паспортные данные и восстанавливать пароль походом в офис? :)


      1. Indexator
        23.11.2015 20:19

        Зачем же? Для восстановления пароля есть и другие стандартные, более надежные и проверенные временем способы: через электронную почту, телефон или через любой другой альтернативный канал связи.

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

        А подсказка — это вообще не механизм восстановления пароля, и даже не механизм защиты, а совсем наоборот — это механизм понижения стойкости защиты и вообще дыра в безопасности.


        1. shoomyst
          23.11.2015 21:58

          Поколение 2000-х? :) Механизм восстановления по секретному вопросу использовали эти самые почтовики. Ну а про мобильные тогда вообще мало кто знал, тем более об автоматизированной рассылке смс по ним


          1. Indexator
            23.11.2015 23:18

            Да не, постарше буду. :)

            В том-то и дело, что те времена уже давным-давно прошли, мир ушел вперед и продолжает движение с бешеным ускорением, технологии постоянно меняются и развиваются, а этот уязвимый механизм многими до сих пор используется… Да и возможность восстановления по альтернативному почтовому адресу была всегда.

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

            Причем, по своему опыту знаю случай, когда сервис даже не дает возможности изменить уже заданный ранее ответ на подсказку. Т.е. задал один раз и все, ни шагу назад! И это современный доменный регистратор! Доменный регистратор, Карл!!!

            Да что далеко ходить, даже в современной венде используется этот уязвимый механизм подсказок, хоть и наравне с другими механизмами. Но мы ведь прекрасно понимаем, что стойкость системы определяется ее самым слабым звеном…


            1. shoomyst
              23.11.2015 23:51

              Долго можно спорить и разбирать каждый отдельный случай. Кто-то тянет этот функционал по привычке, винда просит ответ для локального аккаунта, который, я даже не представляю, как можно восстановить иначе? Ну а если смущает безопасность, то всегда можно ввести какой-нибудь мусор на 500 символов, что я собственно сам и делаю — в keepass для таких сервисов храню как сам пароль, так и рандомный хеш в качестве ответа на секретный вопрос :)


              1. Indexator
                24.11.2015 03:14

                И это правильно, но не каждый среднестатистический пользователь до этого догадается, скорее вообще не придаст должного значения, вот если бы ему прямо говорили «задайте резервный пароль», еще куда ни шло… :)

                PS:
                Для локального аккаунта еще существует вариант диска сброса пароля.


        1. Indexator
          25.11.2015 14:20

          более надежные
          Имел ввиду «более удобные», конечно же…


  1. lightjedi
    24.11.2015 15:24

    На самом деле соль совсем не должна быть случайной. Достаточно уникальности. Одно не следует из другого, хотя при длинных векторах случайные соли уникальны с большой вероятностью.

    Логин является плохой солью, потому что он может повторяться для разных сервисов; если используется один и тот же пароль, это будет заметно.

    Интересно, что рекомендуется использовать bcrypt (не самое новое решение) и tweakable encryption (достаточно свежий подход). На самом деле bcrypt хорош только против взлома на GPU, на более продвинутых архитектурах он будет не сильно лучше PBKDF2 с тем же числом итераций.


  1. grumbler66rus
    25.11.2015 09:42

    Стоило в названии статьи указать, что речь исключительно про PHP, например «Риски и проблемы хеширования паролей в веб-приложениях PHP»