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

 

Intro

Последнее время новостная лента пестрит постами об утечках паролей с крупных ресурсов. Если ты следишь за новостями мира ИБ, то наверняка слышал об утечке 6,46 миллиона хешей паролей крупнейшей социальной сети для профессионалов — LinkedIn. По сей день этот инцидент расследуют самые крутые спецы ФБР. Можно сказать, что последние полгода организация утечек паролей является негласным трендом в кругах анонимусов.

passes

За это время были опубликованы хеши таких популярных ресурсов, как Last.fm, Yahoo Voice, eHarmony, NVIDIA. Небезызвестная компания Rapid 7 провела анализ 165 тысяч хешей, слитых с LinkedIn, и составила инфографику самых популярных (она приложена в качестве иллюстрации к этой статье).

На первый взгляд, пользователи сами виноваты, что используют такие простые для восстановления пароли. Но так ли это? В конечном счете компании удалось расшифровать и проанализировать все пароли, как сложные, так и простые. Механизмы защиты паролей, фигурировавшие в историях этих утечек, настолько примитивны, что при желании для восстановления исходных данных можно использовать минимальные ресурсы. Стоит ли в таком случае говорить о том, насколько уязвимыми мы становимся с неустойчивыми хешами, которые легко вскрываются ресурсами китайской ОЕМ-видеокарты стоимостью 42 доллара? Возможно ли существенно усложнить злоумышленникам восстановление паролей пользователей с утекшей БД? Возможно! Сегодня я расскажу тебе о том, как снизить риски восстановления паролей из неустойчивых хешей.

 

Ошибка начинающих криптоаналитиков

Многие начинающие криптоаналитики считают, что схема SHA1(SHA1(SHA1(..($hash)))) будет добавлять раунды в SHA1.

Все бы было именно так, если бы не одно фундаментальное свойство алгоритма SHA1. Из него следует, что вычислительно невозможно написать такую функцию SHA1000, которая будет эквивалентна тысячекратному вложенному вызову SHA1 и при этом будет легко вычислима. Обрати внимание, что результат SHA1(SHA1(..($hash))) — это не то же самое, что добавить больше раундов внутрь SHA1, так как там есть еще пре- и постобработка. Результатом такого хеширования может стать невыгодный во всех отношениях расход процессорных ресурсов.

 

Атака на хеши

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

  1. Брутфорс (Brutforce) — различают основные три типа брутфорса:
    • тупой (dummy) — перебор всех возможных значений. Этот принцип довольно устарел и в чистом виде уже не применяется нигде;
    • шаблонный (template) — перебор с использованием специальных шаблонов regexp, а также с применением различного рода словарей;
    • экстремальный (extreme) — перебор при помощи средств GPU. Современные видеокарты, поддерживающие технологию CUDA, AMD OpenCL, позволяют перебирать все возможные комбинации шестизначных паролей меньше чем за минуту, а семизначные — не больше чем за шесть минут. Используя технологии типа CrossFire и Stream, можно и вовсе объединять видеокарты в один массив для более эффективного перебора. Скорость перебора значения при этом может быть рассчитана по формуле:Среднее время (t) перебора (W) на N-м количестве видеокарт
      t = ((W)/N1 + N2 + N3 + … + Nn)/2
  2. Перебор по «Rainbow tables» (радужные таблицы) — по сути, это тот же перебор, только с использованием заранее сгенерированных специальным образом таблиц. Очень эффективно применять против длинных паролей. Скорость перебора ограничивается лишь скоростью процессора и быстродействием памяти.
Схема упрощенной радужной таблицы с длиной цепочек, равной трем. R1, R2, R3 — функции редукции, H — функция хеширования
Схема упрощенной радужной таблицы с длиной цепочек, равной трем. R1, R2, R3 — функции редукции, H — функция хеширования

 

Автор md5crypt подчеркнул небезопасность данного алгоритма и призвал перейти на более стойкие методы хеширования паролей.

Под впечатлением от утечки нескольких миллионов хешей паролей пользователей сайтов LinkedIn, eHarmony и Last.fm, Пол-Хеннинг Камп (Poul-Henning Kamp), объявил, что созданную им в 1995 году реализацию системы хеширования паролей md5crypt больше нельзя считать безопасной.

По словам Пола-Хеннинга Кампа, md5crypt исчерпал себя как алгоритм хеширования паролей. Современные инструменты подбора паролей, способные проверить миллион комбинаций в секунду, благодаря задействованию средств GPU-акселерации могут восстановить любой семисимвольный пароль по хешу md5 меньше чем за шесть минут, а для шестисимвольного перебор всех значений и вовсе будет составлять примерно минуту. Так как во многих системах для хеширования паролей по умолчанию по-прежнему используется md5crypt, Пол-Хеннинг Камп призвал пользователей и разработчиков ОС перейти на более стойкие алгоритмы.

Пол-Хеннинг не указывает на конкретный алгоритм, но советует использовать некоторые методы повышения затрат вычислительных ресурсов, например цикличное вложенное хеширование или комбинацию результатов разных алгоритмов хеширования. Для сайтов с более чем 50 тысячами пользовательских аккаунтов Пол-Хеннинг Камп порекомендовал использовать собственный модифицированный алгоритм, базирующийся на стойких хешах, таких как SHA (чтобы организовать процесс подбора паролей для нестандартного алгоритма, дополнительно потребуется определить и воссоздать его логику).

 

Как снизить риски?

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

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

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

md5($pass.$salt)
md5($salt.$pass)
md5(md5($pass))
md5(md5(md5($pass)))
vBulletin < v3.8.5
md5(md5($salt).$pass)
md5($salt.md5($pass))
md5($salt.$pass.$salt)
md5(md5($salt).md5($pass))
md5(md5($pass).md5($salt))
md5($salt.md5($salt.$pass))
md5($salt.md5($pass.$salt))
vBulletin > v3.8.5
md5($username.0.$pass)
md5(strtoupper(md5($pass)))
sha1($pass.$salt)   
sha1($salt.$pass)   
sha1(sha1($pass))   
sha1(sha1(sha1($pass)))
sha1(strtolower($username).$pass)

Приведем пример простейшей маринованной защиты с применением статической соли от радужных таблиц md5(sha1(md5($pass))) на PHP:

$password = "passwd";  // Введенный пользователем простой пароль, который с вероятностью 99,9% будет в радужной таблице типа low-alpha
echo sha1($password);  // По понятным причинам мы больше не используем алгоритм md5 для хеширования ;-)
$salt = "S$4(!@#$%^17BB5G)$11_S2";  // Используя случайный набор символов, мы можем изменить значение хеша
echo sha1($salt . $password); // А вот хеш для маринованного пароля с солью
// Такая комбинация пароля и его хеша не найдется ни в одной радужной таблице

Статическая соль и подобные конструкции могут служить достаточно хорошо до тех пор, пока структура этих конструкций и соль хранятся в тайне. Если же злоумышленник сможет разреверсить алгоритм и узнать зашитый статический (что важно!) секретный ключ хеширования, то уже понятно, что ему не составит труда модифицировать под себя свою «радужную таблицу».

Hashcat поддерживает восстановление пароля из «соленых» хешей
Hashcat поддерживает восстановление пароля из «соленых» хешей

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

$hash = sha1($user_id . $password);

Идеальный вариант — генерировать полностью уникальную соль:

// Генерируем случайную строку длиной в 22 символа  
function unique_salt() {  
  return substr(sha1(mt_rand()),0,22);
} 

$unique_salt = unique_salt();
$hash = sha1($unique_salt . $password);  // Заносим в переменную hash уникальный маринованный хеш

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

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

Жизненный цикл популярных криптографических алгоритмов
Жизненный цикл популярных криптографических алгоритмов

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

Хочешь повысить безопасность — придется потратиться на ресурсы и время, причем соотношение ресурсы/время прямо пропорционально.

function myhash($password, $unique_salt) {
$salt = "S$4(!@#$%^17BB5G)$11_S2";  
$hash = sha1($unique_salt . $password);  
// В цикле исполняем функцию 1000 раз и только потом возвращаем результат
for ($i = 0; $i < 1000; $i++) {  
$hash = sha1($hash);  
}   
return $hash;  
}

Если злоумышленнику для того, чтобы сломать восьмисимвольный пароль, на мощной видеокарте потребуется около 55 часов, то после применения метода замедленного хеширования перебор всех значений уже составит семь лет. PROFIT! 😉

Удобнее для замедления хеш-функций использовать различные криптографические алгоритмы, встроенные с PHP 4.0.32 и реализуемогчерез функцию crypt():

<?php
if (CRYPT_STD_DES == 1) {
// Прототип функции crypt следующий:  crypt (string str, [string salt])
echo 'Standard DES: ' . crypt('sanjar_satsura', 'rl') . "\n";
}

if (CRYPT_EXT_DES == 1) {
echo 'Extended DES: ' . crypt('sanjar_satsura', '_J9..sanj') . "\n";
}

if (CRYPT_MD5 == 1) {
// Желательно его не использовать, хотя если важна все-таки скорость работы, то оптимальным вариантом
// этой функции является алгоритм MD5
echo 'MD5:  ' . crypt('sanjar_satsura', '$1$sanjar$') . "\n";
}

if (CRYPT_BLOWFISH == 1) {
echo 'Blowfish: ' . crypt('sanjar_satsura', '$2a$07$usesomesillystringforsalt$') . "\n";
}

if (CRYPT_SHA256 == 1) {
echo 'SHA-256:  ' . crypt('sanjar_satsura', '$5$rounds=5000$usesomesillystringforsalt$') . "\n";
}

if (CRYPT_SHA512 == 1) {
echo 'SHA-512:  ' . crypt('sanjar_satsura', '$6$rounds=5000$usesomesillystringforsalt$') . "\n";
}
?>

Если второй аргумент функции crypt не будет передан, он будет выбран случайным образом, так что соль генерируется полностью случайно. Золотой серединой метода замедления хеш-функции является применение криптоалгоритма Blowfish. Blowfish — это способ шифрования с медленным алгоритмом разделения ключа (сам алгоритм довольно быстр после выполнения разделения ключа [key scheduling], а также когда необходимо зашифровать большое сообщение с одним ключом). По современным меркам ИБ такой код должен обеспечить максимальную безопасность.

function blowfish_hash($password, $unique_salt) {  
 // Соль для Blowfish должна быть длиной в 22 символа  
 return crypt($password, '$2a$10$'.$unique_salt); 
}

 

Почему существует уязвимость?

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

Еще недавно (примерно шесть лет назад) коллизии для криптографических алгоритмов хеширования могли показаться фантастикой, а сегодня этим уже никого не удивишь. Некогда «устойчивые» алгоритмы хеширования MD4/MD5, ставшие стандартами де-факто во многих проектах и решениях в области продуктов информационной безопасности, теперь полностью безнадежны и должны быть отправлены только в одном направлении… да, ты угадал: в самое /dev/null :).

Примерно то же самое происходило в 1995 году с алгоритмом DES. Последствия, которые он оставил, до сих пор дают о себе знать. Но почему, осознавая, что алгоритм более не безопасен, мы продолжаем его использовать? Возможно, потому, что просто привыкли к этим алгоритмам хеширования, ведь они прочно вошли в нашу жизнь. Хотя есть еще один важный момент: лень-матушка, и от нее никуда не денешься, ага :). Более подробно о коллизиях криптографических функций и типах атак ты можешь прочитать в моей статье «Опасный двойник», опубликованной в номере 159 нашего журнала.

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

В качестве примера можно привести набор замечательных ошибок, недавно обнаруженных в реализации класса RSACryptoServiceProvider библиотеки .NET Framework. Как оказалось, заявленные в классах RSACryptoServiceProvider и DSACryptoServiceProvider методы SignHash имеют глупейшую ошибку, которая заключается в псевдорандомизации трех из четырех блоков. Итог: +75% к атакам на RSA шифрование в vm .NET.

Многие журнальные и научные статьи любят описывать криптографические продукты в терминах алгоритмов и длины ключей. Алгоритмы благозвучны: их описание может быть немногословным и их легко сравнивать друг с другом. «128-битные ключи означают высокую степень защиты». «Тройной DES означает высокую степень защиты». «40-битные ключи означают низкий уровень защиты». «2048-битный RSA лучше 1024-битного RSA».

Но в реале все не так просто. Более длинные ключи не всегда означают лучшую защиту. Давай сравним криптографический алгоритм с замком на твоей входной двери. Большинство доверенных замков имеют четыре металлических штифта, каждый из которых может находиться в одном из десяти положений. Ключ устанавливает штифты в особой комбинации. Если ключ установит их все правильно, замок откроется. Таким образом, может быть только 10 тысяч различных ключей, и взломщик, готовый испробовать их все, обязательно попадет к тебе в дом. Но улучшенный замок с десятью штифтами, дающий 10 миллиардов возможных ключей, часть из которых, несомненно, будет забракована или будет содержать дефект, естественно, не сделает твое жилище безопаснее. Правильные хеш-крекеры не испытывают каждый возможный ключ (атака «в лоб»), большинство даже не настолько хитры, чтобы взломать замок (криптографическая атака на алгоритм), и используют готовые инструменты и рекомендации. Лучшие замки не спасут от таких атак, пока будут существовать ошибки в алгоритмах проектирования.

 

Заключение

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

 

Оставить мнение

Check Also

Espruino Pico. Учимся программировать USB-микроконтроллер на JavaScript и делаем из него токен авторизации

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