Все о хешировании

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

Что такое хеширование?

Хеширование — это превращение каких-либо данных (малого или большого объема) в относительно короткий кусок информации как строка или число.

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

$data = 'Hello World!';
$hash = md5($data);
echo $hash;
// Выводит: b10a8db164e0754105b7a99be72e3fe5

Функция md5() всегда возвращает строку длиной в 32 символа, состоящая из шестнадцатеричных символов. Технически, она может быть представлена 128-битным (16-байтным) числом. Вы можете обработать функцией md5() короткие и очень большие строки, но результат всегда будет длиной в 32 символа. Одно это должно вам подсказать что хеширование это необратимый процесс.

Использование хеш-функций для хранения паролей

Обычный процесс при регистрации пользователей:

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

Процесс входа под своей учетной записью:

  1. Пользователь вводит имя (или e-mail) и пароль;
  2. Скрипт обрабатывает пароль той же хеш-фунцией;
  3. Скрипт находит запись пользователя в базе данных и читает сохраненный хешированный пароль;
  4. Оба значения сравниваются, и если они совпадают, доступ дается.

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

Проблема №1: коллизия хеша

Коллизия хеша случается когда две разные исходные строки дают одинаковый хеш. Вероятность этого зависит от той функции, которая используется. Например, некоторые старые скрипты используют функцию crc32() для генерации хеша. Она генерирует 32-битовое число как результат. Давайте хешируем пароль:

echo crc32('supersecretpassword');
// Выводит: 323322056

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

<?php
set_time_limit(0);
$i = 0;
 
while(TRUE) {
    if(crc32(base64_encode($i)) == 323322056) {
        echo base64_encode($i);
        break;
    }
     
    $i++;
}
?>

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

Как это предотвратить?

В наши дни мощный домашний компьютер может генерировать почти миллиард хешей в минуту. Таким образом нам нужна хеш-функция с очень большим диапазоном. Функция md5(), генерирующая 128-битовые хеши, будет более подходящая. Невозможно совершить столько итераций, чтобы найти коллизии.

А еще лучше использовать функцию sha1(), которая генерирует 160-битный хеш.

Проблема №2: радужные таблицы

Радужная таблица составляется из часто используемых слов (и иногда их комбинаций) и их хешей. У этих таблиц могут быть миллионы рядов. Например, вы можете обрабатывать словарь и генерировать хеши для каждого слова. Вы можете комбинировать слова вместе и генерировать хеши для их комбинаций. Но и это еще не все: вы можете добавлять до (после и между) слов цифры и также записывать в таблицу. А так как хранение информации дешево, можно создавать гигантские радужные таблицы. А если база данных была украдена, то довольно легко подобрать пароли некоторых пользователей.

Как это предотвратить?

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

// Этот пароль легко найти в радужной таблице, т.к. он состоит из 2 английских слов:
$password = 'easypassword';
 
// Добавляем к нему "соль":
$password .= 'f#@V)Hu^%Hgfds';
 
// Генерируем хеш:
echo sha1($password);

Проблема №3: скорость хеширования

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

Если пароль состоит из больших, маленьких латинских букв и цифр, это составляет 62 (26+26+10) возможных символов. Из строки длиной в 8 символов получается 628 возможных вариантов. Это составляет чуть более 218 триллионов. Со скоростью в 1 миллиард хешей в секунду, эти значения возможно перебрать за 60 часов.

Для пароля длиной в 6 символов понадобиться около 1 минуты. А требование паролей с длиной 9–10 символов может вызвать раздражение у ваших пользователей.

Как это предотвратить?

А представьте что вы используете хеш-функцию, которая занимает в 1000 раз больше времени для своего выполнения. Теперь потенциальный взломщик будет иметь скорость только в миллион хешей. А 60 часов превратятся в 7 лет:

$password = '12345';
 
for($i = 0; $i < 1000; $i++) {
    $password = sha1($password);
}

Соберем все вместе

А вот перед вами функция, которая учитывает все оговоренные моменты:

function my_hash($password) {
    $salt = 'f#@V)Hu^%Hgfds';
 
    $password .= $salt;
 
    for($i = 0; $i < 1000; $i++) {
        $password = sha1($password);
    }
 
    return $password; 
}