Хеширование - преобразование массива входных данных произвольной длины в (выходную) битовую строку фиксированной длины, выполняемое определённым алгоритмом. Функция, реализующая алгоритм и выполняющая преобразование, называется «хеш-функцией»(реже «функцией свёртки»). Исходные данные называются входным массивом, «ключом» или «сообщением». Результат преобразования (выходные данные) называется «хешем», «хеш-кодом», «хеш-суммой», «сводкой сообщения».
Хранение логин/пароля в БД
Рассмотрим пример хранения пары логин/пароль. Первый вариант.
В качестве примера во втором варианте первичным ключом является логин, т.е. столбец usr
.
Выбор столбца в качестве первичного ключа остаётся за Вами.
Хранение пароля в открытом виде
Храним пароль как есть. Например, если ввели пароль "12345", то он так и сохранится. Минус в том, что при взломе БД злоумышленник получает все пароли в явном виде.
usr | passwd |
user | password |
prof | 123456 |
pety | qwerty |
ТОП-100 паролей
Храним пароль в зашифрованном
Самый простой способ хранить пароли в базе это положить их в таблице пользователей в открытом виде. Но при таком подходе, в случае попадания базы в руки злдоумышленника, все пароли 100% становятся ему известны.
Чтобы этого не произошло, пароль лучше хранить в зашифрованном виде, использовав, к пример, алгоритмы шифрования md5
, sha1
и т.п.
Самая распространённая хэш функция это md5
.
Применяя md5
, Вы всегда будете получать в качестве результата строку размером 32 символа. Но эти символы будут в шестнадцатеричном виде. Вы можете помещать в функцию md5() строки и числа любой длины, но на выходе всегда будете получать результат в 32 символа.
<?php
$h = md5("passowrd");
?>
usr | passwd |
user | 5f4dcc3b5aa765d61d8327deb882cf99 |
prof | e10adc3949ba59abbe56e057f20f883e |
pety | d8578edf8458ce06fbc5bb76a58c5ca4 |
Но и тут не всё так просто. ПРОБЛЕМА №1 - Коллизия хеш-функции
Использование хэш-функций →
Использование коллизии
Коллизия хеш-функции возникает, когда она выдает одинаковый результат на разные входные данные. Конечно же, вероятность этого достаточно мала, и зависит от длины хэша. Однако устаревшая (но до сих пор иногда используемая) функция crc32
возвращает в качестве хэша 32-битное целое число. Т.е., чтобы подобрать пароль к такому хэшу, по теории вероятности нужно получить 2^32 = 4 294 967 296 различных хэшей. Даже на хостинге crc32
может работать примерно со скоростью 350 000 хешей в секунду(посчитайте сами сколько нужно секунд, чтобы взломать такой хэш).
Конечно же это не относится md5
(128-битный хеш) и тем более sha1
(160-битный хеш). Использовать их коллизию практически невозможно, хотя есть статья и ещё одна...
Представим что "плохой дядька" украл базу данных вместе с хэшированным паролем. У него не будет возможности преобразовать 323322056
обратно в "supersecretpassword", однако, благодаря простому скрипту он можем подобрать другой пароль, который в хэшированном виде будет точно такой же как и тот, который находится в базе.
К примеру для нашего пароля "supersecretpassword" найдётся строка "MTIxMjY5MTAwNg==" точно с таким же хешем.
echo crc32("supersecretpassword");
echo crc32("MTIxMjY5MTAwNg==");
Пример коллизии →
Даже на домашнем компьютере можно использовать миллионы хэш функций в секунду. Поэтому необходимо использовать такую хэш функцию, которая сгенерировала как можно большее значение.
Рекомендую использовать алгоритмы линейки SHA
(на данный момент практически заверщён переход от SHA-1 к стандартам версии SHA-2). SHA-2
- это семейство криптографических функций SHA224, SHA256, SHA384 и SHA512. SHA224 и SHA384 являются по сути аналогами SHA256 и SHA512 соответственно, только после расчета свертки часть информации в ней отбрасывается.
При использовании более стойких алгоритмы для хеширования, часто встает вопрос производительности. Действительно, некоторые алгоритмы потребляют больше ресурсов.
Пример скорости перебора хешей (единицы измерения — мегахэши в секунду, то есть количество ), полученных на карточке AMD Radeon 7990:
- MD5: 16000 M/s
- SHA-1: 5900 M/s
- SHA256: 2050 M/s
- SHA512: 220 M/s
И тут не всё так просто. ПРОБЛЕМА №2 - Радужная таблица
Радужная таблица
Радужные таблицы состоят из хэшей наиболее часто употребляемых паролей — имен, дат рождения, названий животных и т.п. Эти таблицы могут включать миллионы, миллиарды значений, но работа с ними относительно быстра, и проверить хэш на соответствие одному из значений не составляет никакого труда.
Рассмотрим ситуацию. У Вас есть хешированный пароль пользователя, и хранится он в таблице базы данных. Даже если злоумышленник получает доступ к нашей базе данных он не сможет определить исходный пароль. Но что если он сравнивает все хэшированные пароли друг с другом, и находит некоторые из них как быть?
user | 5f4dcc3b5aa765d61d8327deb882cf99 |
... | ... |
pety | 5f4dcc3b5aa765d61d8327deb882cf99 |
Мы уже знаем две строки могут иметь одинаковый хеш, только если они обе равны(без учёта коллизии). Значит, если атакующий видит хеши он может сделать вывод о том что пароли для этих учетных записей одинаковые. Если атакующий, знает хотя бы один пароль к аккаунту, он может его использовать для получения доступа ко всем аккаунтам с этим паролем.
Решение состоит в использовании случайного числа при генерации хеша, называемым солью.
"Солим" пароль
Одним из решением проблемы радужных таблиц является использование случайного числа при генерации хеша, так называемая соль.
$salt = "f?*-Q@t03#6_z";
echo sha1($salt . "password");
Использование "соли" →
Так для нашего примера применим соль.
usr | passwd |
user | 8da1a876eabe199515de1766b87a602a5d06d378 |
prof | 3c9ced61cf158c57bff7cad5d72ae9ceb17e565a |
pety | 58131ab13b4af6894bee5ea6b6999cd90c159239 |
Статическая соль и тому подобные конструкции могут служить достаточно хорошо, пока структура этих конструкций и соль хранятся в тайне. Если же злоумышленник вызнает секрет хэширования - он с легкостью сможет модифицировать под него свою радужную таблицу.
Одним из решений может быть генерация уникальной соли для каждого пользователя
Уникальная соль
Каждый раз, когда мы генерируем хэш пароля, необходимо использовать случайную соль. Просто нужно генерировать случайные числа(строки) определенной длины и добавить его в простой текстовый пароль, а затем его хеш. Таким образом, даже если пароли для двух аккаунтов одинаковые с генерированный хеш не будет одинаковым, потому что числа соли в одинаковых случаях различные.
usr | passwd | salt |
user | 6aa747272c7d077d0fd1672ae48ce99da25d981179761ed37949c576e91959ec | }{@kE$ |
prof | a3910b3c32a672ea9ee70866bc144dcba715bc9172d44debb8482f42aad6d503 | #_абв78 |
pety | 08ad24df81e43e013c5cf85643b2a85aae67e2369d8140ed66d53c37bec25557 | ~~$~~ |
Хеш-функция SHA256
+ соль
Конечно, уникальную соль придется вносить в базу данных, но даже получив доступ к ней, злоумышленник вряд ли сможет сгенерировать несколько миллионов радужных таблиц.
$salt = "}{@kE$";
echo hash("sha256", $salt . "password");
Использование уникальной "соли" →
Встроенная функция hash
Генерировать уникальную соль можно различными способами от самописных функций до строенных в PHP.
Большинство функций хэширования разрабатывались, учитывая то, что они часто используются для расчёта контрольных сумм каких-то значений или файлов с проверкой целостности данных.
Казалось бы - чем быстрее отработает функция, тем лучше. Чем быстрее сгенерируется хэш, тем быстрее наш пользователь сможет зарегистрироваться и начать работать. Однако чем больше скорость хэширования, тем быстрее его сможет подобрать и хакер.
ПРОБЛЕМА №3 - Скорость хеширования
Принудительное замедление
Как говорилось ранее, компьютер с мощной графической картой может высчитывать миллион хэшей за секунду. Злоумышленники могут применить "грубую силу"(bruteforce), проверяя каждый единственно возможный пароль (проводя полный перебор всех возможных вариантов).
Если в пароле используются символы в нижнем и верхнем регистрах и цифры, то общее количество возможных символов составит 62 (26+26+10).
Для пароля длиной в 8 символов, существует 62^8
различных комбинаций (порядка 218 триллионов). Со скоростью в 1 миллиард хэшей в секунду (достаточно маленькая для брутфорс-атаки), пароль будет сломан примерно за 60 часов. А для наиболее распространенной длины пароля в 6 символов, длительность расшифровки составит меньше двух минут.
Лучше использовать более медленные функции хэширования или например можно замедлить функцию хэша вручную:
function myhash($passwd, $salt) {
$hash = sha1($salt . $passwd);
sleep(1);
return $hash;
}
Используя ее, вместо 60 часов, хакер будет ломать 8-символьный пароль около 7 лет.