Радужная таблица
Радужная таблица — специальный вариант таблиц поиска для обращения криптографических хеш-функций, использующий механизм разумного компромисса между временем поиска по таблице и занимаемой памятью
Радужные таблицы используются для вскрытия паролей, преобразованных при помощи сложнообратимой хеш-функции, а также для атак на симметричные шифры на основе известного открытого текста.
Использование функции формирования ключа с применением соли делает эту атаку неосуществимой.
Предпосылки к появлению
Компьютерные системы, которые используют пароли для аутентификации, должны каким-то образом определять правильность введённого пароля. Самым простым способом решения данной проблемы является хранение списка всех допустимых паролей для каждого пользователя. Минусом данного метода является то, что в случае несанкционированного доступа к списку злоумышленник узнаёт все пользовательские пароли. Более распространённый подход заключается в хранении значений криптографической хеш-функции от парольной фразы. Однако большинство хешей быстро вычисляется, поэтому злоумышленник, получивший доступ к хешам, может быстро проверить список возможных паролей на валидность. Чтобы избежать этого, нужно использовать более длинные пароли, тем самым увеличивая список паролей, которые должен проверить злоумышленник. Для простых паролей, не содержащих соли, взломщик может заранее подсчитать значения хешей для всех распространённых и коротких паролей и сохранить их в таблице. Теперь можно быстро найти совпадение в заранее полученной таблице. Но чем длиннее пароль, тем больше таблица, и тем больше памяти необходимо для её хранения. Альтернативным вариантом является хранение только первых элементов цепочек хешей. Это потребует больше вычислений для поиска пароля, но значительно уменьшит количество требуемой памяти. А радужные таблицы являются улучшенным вариантом данного метода, которые позволяют избежать коллизий.
Пусть у нас есть хеш-функция H с длиной хеша n и конечное множество паролей P. Наша цель — создать структуру данных, которая для любого значения хеша h может либо найти такой элемент p из P, что H(p)=h, либо определить, что такого элемента не существует. Простейший способ сделать это — вычислить H(p) для всех p из P, но для хранения такой таблицы потребуется Θ(|P|n) памяти, что слишком много.
Цепочки хешей — метод для уменьшения этого требования к объёму памяти. Главная идея — определение функции редукции R, которая сопоставляет значениям хеша значения из P. Заметим, что R не является обращением хеш-функции. Начиная с исходного пароля и попеременно применяя к каждому полученному значению H и R, мы получим цепочку перемежающихся паролей и хешей. Например, для набора паролей длиной в 6 символов и хеш-функции, выдающей 32-битные значения, цепочка может выглядеть так:
- [math]\displaystyle{ \mathbf{aaaaaa}\,\xrightarrow[\;H\;]{}\,\mathrm{281DAF40}\,\xrightarrow[\;R\;]{}\,\mathrm{sgfnyd}\,\xrightarrow[\;H\;]{}\,\mathrm{920ECF10}\,\xrightarrow[\;R\;]{}\,\mathbf{kiebgt} }[/math]
К функции редукции предъявляется единственное требование: возвращать значения из того же алфавита, что и пароли.
Для генерации таблицы мы выбираем случайное множество начальных паролей из P, вычисляем цепочки некоторой фиксированной длины k для каждого пароля и сохраняем только первый и последний пароль из каждой цепочки.
Для каждого хеша h, значение которого мы хотим обратить (найти соответствующий ему пароль), вычисляем последовательность R(…R(H(R(h)))…). Если какое-то из промежуточных значений совпадёт с каким-нибудь концом какой-либо цепочки, мы берём начало этой цепочки и восстанавливаем её полностью. С высокой вероятностью полная цепочка будет содержать значение хеша h, а предшествующий ему пароль будет искомым.
Для примера, указанного выше, если у нас изначально есть хеш 920ECF10, он породит следующую последовательность:
- [math]\displaystyle{ \mathrm{920ECF10}\,\xrightarrow[\;R\;]{}\,\mathbf{kiebgt} }[/math]
Поскольку kiebgt
является концом цепочки из нашей таблицы, мы берём соответствующий начальный пароль aaaaaa
и вычисляем цепочку, пока не найдём хеш 920ECF10:
- [math]\displaystyle{ \mathbf{aaaaaa}\,\xrightarrow[\;H\;]{}\,\mathrm{281DAF40}\,\xrightarrow[\;R\;]{}\,\mathrm{sgfnyd}\,\xrightarrow[\;H\;]{}\,\mathrm{920ECF10} }[/math]
Таким образом, искомый пароль — sgfnyd
.
Стоит заметить, что восстановленная цепочка не всегда содержит искомое значение хеша h. Такое возможно при возникновении коллизии функции H или R. Например, пусть дан хеш FB107E70, который на определенном этапе порождает пароль kiebgt:
- [math]\displaystyle{ \mathrm{FB107E70}\,\xrightarrow[\;R\;]{}\,\mathrm{bvtdll}\,\xrightarrow[\;H\;]{}\,\mathrm{0EE80890}\,\xrightarrow[\;R\;]{}\,\mathbf{kiebgt} }[/math]
Но FB107E70 не появляется в цепочке, порождённой паролем aaaaaa
. Это называется ложным срабатыванием. В этом случае мы игнорируем совпадение и продолжаем вычислять последовательность, порождённую h. Если сгенерированная последовательность достигает длины k без хороших совпадений, это означает, что искомый пароль никогда не встречался в предвычисленных цепочках.
Содержимое таблицы не зависит от значения обращаемого хеша, она вычисляется заранее и используется лишь для быстрого поиска. Увеличение длины цепочки уменьшает размер таблицы, но увеличивает время поиска нужного элемента в цепочке.
Простые цепочки хешей имеют несколько недостатков. Самый серьёзный — возможность слияния двух цепочек в одну (генерация одного и того же значения в разных цепочках). Все значения, сгенерированные после слияния, будут одинаковыми в обеих цепочках, что сужает количество покрываемых паролей. Поскольку прегенерированные цепочки сохраняются не целиком, невозможно эффективно сравнивать все сгенерированные значения между собой. Как правило, об отсутствии коллизий в хеш-функции H заботится сторона, обеспечивающая шифрование паролей, поэтому основная проблема кроется в функции редукции R.
Другая серьёзная проблема — подбор такой функции R, которая будет генерировать пароли с требуемым покрытием и распределением. Ограничение выходного алфавита является серьёзным ограничением для выбора такой функции.
Радужные таблицы
Радужные таблицы являются развитием идеи таблицы хеш-цепочек. Они эффективно решают проблему коллизий путём введения последовательности функций редукции R1, R2, …, Rk. Функции редукции применяются по очереди, перемежаясь с функцией хеширования: H, R1, H, R2, …, H, Rk. При таком подходе две цепочки могут слиться только при совпадении значений на одной и той же итерации. Следовательно, достаточно проверять на коллизии только конечные значения цепочек, что не требует дополнительной памяти. На конечном этапе составления таблицы можно найти все слившиеся цепочки, оставить из них только одну и сгенерировать новые, чтобы заполнить таблицу необходимым количеством различных цепочек. Полученные цепочки не являются полностью свободными от коллизий, тем не менее, они не сольются полностью.
Использование последовательностей функций редукции изменяет способ поиска по таблице. Поскольку хеш может быть найден в любом месте цепочки, необходимо сгенерировать k различных цепочек:
- первая цепочка строится в предположении, что искомый хеш встретится на последней позиции в табличной цепочке, поэтому состоит из единственного значения Rk(h);
- вторая цепочка строится в предположении, что искомый хеш встретится на предпоследней позиции в табличной цепочке, поэтому выглядит так: Rk(H(Rk−1(h)));
- аналогично, наращивая длину цепочки и применяя функции редукции с меньшими номерами, получаем остальные цепочки. Последняя цепочка будет иметь длину k и содержать все функции редукции: Rk(H(Rk−1(H(…H(R1(h))…))))
Также изменится и определение ложного срабатывания: если мы неверно «угадаем» позицию искомого хеша, это будет отбрасывать только одну из k сгенерированных цепочек; при этом всё ещё может оставаться возможность найти верный хеш для данной табличной цепочки, но на другой позиции.
Хотя радужные таблицы требуют отслеживания большего количества цепочек, они имеют бо́льшую плотность количества паролей на одну табличную запись. В отличие от таблицы хеш-цепочек, применение нескольких функций редукции уменьшает число потенциальных коллизий в таблице, что позволяет её наращивать без опасности получить большое количество слияний цепочек.
Время и память
Радужная таблица создаётся построением цепочек возможных паролей. Создание таких таблиц требует больше времени, чем нужно для создания обычных таблиц поиска, но значительно меньше памяти (вплоть до сотен гигабайт, при объёме для обычных таблиц в N слов для радужных нужно всего порядка N2/3). При этом они требуют хоть и больше времени (по сравнению с простыми методами вроде атаки по словарю) на восстановление исходного пароля, но на практике более реализуемы (для построения обычной таблицы для 6-символьного пароля с байтовыми символами потребуется 2566 = Шаблон:Число блоков памяти, в то время как для радужной — всего 2566·⅔ = Шаблон:Число блоков).
Таблицы могут взламывать только ту хеш-функцию, для которой они создавались, то есть таблицы для MD5 могут взломать только хеш MD5. Теория данной технологии была разработана Philippe Oechslin<ref>Шаблон:Cite web</ref> как быстрый вариант Шаблон:Lang-en2<ref>Доклад Philippe Oechslin на конференции CRYPTO’03 Шаблон:Wayback (PDF)</ref>. Впервые технология использована в программе Ophcrack для взлома хешей LanMan (LM-хеш), используемых в Microsoft Windows. Позже была разработана более совершенная программа RainbowCrack, которая может работать с большим количеством хешей, например, LanMan, MD5, SHA1 и другими.
Следующим шагом было создание программы The UDC, которая позволяет строить Hybrid Rainbow таблицы не по набору символов, а по набору словарей, что позволяет восстанавливать более длинные пароли (фактически неограниченной длины).
Применение
При генерации таблиц важно найти наилучшее соотношения взаимосвязанных параметров:
- вероятность нахождения пароля по полученным таблицам;
- времени генерации таблиц;
- время подбора пароля по таблицам;
- занимаемое место.
Вышеназванные параметры зависят от настроек заданных при генерации таблиц:
- допустимый набор символов;
- длина пароля;
- длина цепочки;
- количество таблиц.
При этом время генерации зависит почти исключительно от желаемой вероятности подбора, используемого набора символов и длины пароля. Занимаемое таблицами место зависит от желаемой скорости подбора 1 пароля по готовым таблицам.
Хотя применение радужных таблиц облегчает использование полного перебора (то есть метода грубой силы — bruteforce) для подбора паролей, в некоторых случаях необходимые для их генерации/использования вычислительные мощности не позволяют одиночному пользователю достичь желаемых результатов за приемлемое время. К примеру, для паролей длиной не более 8 символов, состоящих из букв, цифр и специальных символов !@#$%^&*()-_+=
, захешированных алгоритмом MD5, могут быть сгенерированы таблицы со следующими параметрами:
- длина цепочки — Шаблон:Число
- количество цепочек — Шаблон:Число
- количество таблиц — Шаблон:Число
При этом вероятность нахождения пароля с помощью данных таблиц составит 0,7542 (75,42 %), сами таблицы займут 596 ГиБ, генерация их на компьютере уровня Пентиум-3 Шаблон:Число займёт 3 года, а поиск 1 пароля по готовым таблицам — не более 22 минут.
Однако процесс генерации таблиц возможно распараллелить, например, расчёт одной таблицы с вышеприведёнными параметрами занимает примерно 33 часа. В таком случае, если в нашем распоряжении есть 100 компьютеров, все таблицы можно сгенерировать через 11 суток.
Защита от радужных таблиц
Один из распространённых методов защиты от взлома с помощью радужных таблиц — использование необратимых хеш-функций, которые включают Шаблон:Lang-en2 («соль», «модификатор»). Существует множество возможных схем смешения затравки и пароля. Например, рассмотрим следующую функцию для создания хеша от пароля:
хеш = MD5( пароль + соль )
Для восстановления такого пароля взломщику необходимы таблицы для всех возможных значений соли. При использовании такой схемы, соль должна быть достаточно длинной (6‒8 символов), иначе злоумышленник может вычислить таблицы для каждого значения соли, случайной и различной для каждого пароля. Таким образом два одинаковых пароля будут иметь разные значения хешей, если только не будет использоваться одинаковая соль.
ключ = хеш(пароль + соль) for 1 to 65536 do ключ = хеш(ключ + пароль + соль)