Итак, животрепещущий вопрос, не дающий покоя многим поколениям начинающих php-программистов: как расшифровать строку, зашифрованную функцией md5().
Ответ прост: никак.
Почему
md5() нельзя расшифровать, так как он ничего не шифрует. Он хеширует. Термина "расхешировать" нет.
Хеширование, это преобразование данных произвольного размера в короткую строку (или число) фиксированной длины. Для алгоритма md5 результатом является 32-разрядное 16-ричное число (называемое хешем или дайджестом).
Немного разбираясь в математике, можно сообразить: множество входных значений (строка произвольного размера), практически бесконечно, в то время как количество возможных результатов, представляет собой вполне конечное число – 1632. Следовательно, взаимнооднозначного соответствия между двумя множествами быть не может. Следовательно, зная результирующий хеш, получить исходную строку невозможно, т.к. для одного хеша возможных исходных строк может быть любое количество.
Изначальная задача md5, вообще, ни к хранению паролей, ни к web-программированию не относится. md5 вычисляет контрольные суммы. Например, при передаче большого файла по каналу связи, где возможны ошибки, вместе с ним можно передать его md5-хеш, после чего приемник так же может вычислить хеш полученных данных и сравнить с пришедшим, чтобы удостовериться, что сбоев при передаче не произошло. Либо, вы можете распространять свою программу и выложить у себя на сайте хеш её архива. И пользователь, получивший её из других источников, может проверить, а не залезал ли в неё злой хакер и не прицепил ли в конце какой-нибудь вирус. Одно из свойств md5-алгоритма – значительное отличие хеша, даже при незначительных изменениях во входных данных.
Хеширование паролей
Пароли пользователей обычно хранятся в базе или в файле. Хранить их можно и просто в открытом виде. Однако это является некоторой дырой в безопасности. Т.к. злоумышленник, получивший доступ только для чтения к этой базе, может взломать систему. Случай, когда он получает доступ еще и на запись, не рассматриваем, т.к. никакой md5() здесь уже не поможет.
Вообще-то, дыра в безопасности не в этом. При настройке БД вменяемым администратором, никто посторонний доступа получить не должен. Однако народ у нас администраторам не доверяет и пароли в базе шифрует. Т.е. в большинстве случаев, как уже выяснили, не шифрует, а хеширует.
В большинстве случаев это выглядит следующим образом:
1. При регистрации пользователя, введенный им пароль хешируется. В простейшем случае просто md5($password). И именно хеш заносится в базу. Исходный пароль не сохраняется нигде.
2. При авторизации пользователь вводит пароль, который отправляется на сервер. На сервере он так же хешируется и сравнивается с хешем в базе. Т.е. не хеш из базы преобразуется к нормальному виду, а наоборот.
Злоумышленник, получивший доступ к базе может узнать только хеши, а не исходные пароли, так что его дальнейшая задача усложнится.
Подбор пароля при известном хеше
Как уже было сказано, исходный пароль узнать нельзя, т.к. одному хешу может соответствовать несколько исходных строк. Однако искать исходный пароль и не нужно, достаточно найти любую из этих строк и отправить на сервер в качестве пароля. Две различные строки, дающие одинаковый хеш, называются коллизиями.
Поиск коллизии, имея из исходных данных только хеш, можно осуществить только одним способом – банальным перебором всех значений.
Время на подбор составляет: <количество времени на вычисление хеша для одного значения> * <количество хешей, которые необходимо перебрать>.
Количество перебираемых хешей пропорционально мощности множества всех возможных значений хеша, т.е. 1632, т.е, примерно, 1038. С учетом того, что md5 достаточно длительный для вычисления алгоритм, время подбора выливается на современных компьютерах в достаточно ощутимый промежуток.
Хотя, это время подбора абстрактного значения. Если у вас пароль может содержать латинские символы в обоих регистрах и цифры и иметь длину от 6 до 15 символов, то общее число вариантов – (26*2 + 10)15 – (26*2 + 10)5 = 1026. А с учетом того, что большинство пользователей не будет набирать более 10 символов – 1018. Что уже несколько веселее для взломщика. Если требуется подобрать пароль только к одному пользователю, имея базу по всем пользователям сайта, то делите это число еще и на их количество.
Обращаю внимание, что это средние теоретические цифры. Существует вероятность, что вы наткнетесь на нужный вариант уже на первой итерации. Примерно 1 на 1018.
Так же это все верно при случайной генерации пароля. Однако, треть учетных записей администраторов ломается вводом пароля "god". А еще треть – "admin".
Алгоритм хеширования
В простейшем случае пароль хешируется банально – md5($password). Однако старателями уже собраны достаточно большие базы на md5-хеши. Немного изменив алгоритм, можно сделать их бесполезными.
Простой md5(md5($password)) увеличит время подбора вдвое. Добавьте туда логин, идентификатор пользователя и секретную строку: md5(md5($password.$login).$secret). Перемешайте все это и сделайте так, чтобы никто не узнал точный алгоритм. На каждом сайте немного изменяйте его.
Все, вы неуязвимы :)
Недостатки
С одной стороны плохие люди не могут получить исходный пароль. С другой – хорошие тоже. Наиболее неудобно это при восстановлении пароля. Выслать старый пароль нельзя. Поэтому либо приходится генерировать новый, либо давать пользователю специальную ссылку, где он сам может ввести нужный.
Автоматическая авторизация
Автоматическая авторизация, позволяет пользователю не вводить пароль каждый раз при посещении сайта. Обычно это осуществляется при помощи кук.
В простейшем случае в куках хранятся логин и пароль в открытом виде. Это не совсем здорово, так как куки можно перехватить или просто подсмотреть в браузере. В FireFoxе это вообще делается просто в меню. Поэтому, перед сохранением в куки над этими данными так же следует немного поиздеваться.
Однако здесь нужно помнить следующую вещь – паролем является, по сути, не то, что вводит пользователь в форму, а то, что поступает на вход серверу. Если человек получает куки, которые посылаются серверу, он может послать их с тем же успехом, вне зависимости от того, открытый там пароль или хеш. По большому счету, основное преимущество – хеш сложнее визуально запомнить, чем обычный пароль.
Хранить авторизационные данные в куках можно по разному. Например, можно завести табличку в базе: id-пользователя => рандомный хеш. И хранить в куках этот самых хеш. При каждой авторизации его можно менять и перезаписывать куку. Можно привязать его к IP, чтобы, даже узнав его, плохие дяди не смогли попасть на сайт с другой машины. Но при этом пользователи модемов идут лесом.
Однако этим мы ставим под сомнения все предыдущие выкрутасы с паролем. Ведь, получив доступ к базе, хакер теперь может не подбирать пароль к хешу, а установить себе нужную куку.
Можно хранить в куках хеш по типу того, что в базе. Но нужно опять таки смотреть, чтобы не обесценить все наши попытки зашифровать пароль. Если хранить в базе и в куке одинаковый хеш, то опять все хеширования становятся бессмысленными. Даже при разных алгоритмах нужно долго думать. Например:
$hash2Base = md5($login.$password);
$hase2Cookie = md5($login.md5($login.$password));
Ну и что? Узнав из базы hash2Base, элементарно вычисляем $hash2Cookie = md5($login.$hash2Base) и кладем себе в куку.
Вариантов масса. Главное понимать, что автоматической авторизацией мы открываем еще один вход на сайт и следить за всем приходится вдвое тщательнее.
Простой подбор
Даже не получив доступ к базе, хакер все равно может затеять перебор паролей, запрашивая непосредственно сервер.
Решается это просто – запрещением авторизоваться одному пользователю чаще определенного времени (да даже чаще двух секунд).
Блокированием IP, с которого идет слишком много вопросов. Можно сделать даже на уровне сервера. Правда, достаточно легко обходится путем использования прокси-серверов.
Даже без этих элементарных действий, важная помеха, это скорость ответа сервера. Если сценарий обрабатывается, например, 1 секунду процессорного времени, то чаще проверять пароль просто невозможно.
Подчеркиваю – процессорного времени. Не стоит пытаться защититься при помощи sleep(2) во время авторизации. Эта функция выводит процесс на заданное время из очереди процессов, и он не занимает ресурсов. Послав несколько запросов параллельно, хакер нейтрализует эту задержку.