В очередной раз, когда мы заканчивали проводить аудит информационной безопасности веб-проекта, моя личная бочка с гневом переполнилась негодованием так, что оно перелилось через край в этот пост.
Постараюсь очень лаконично и быстро обрисовать ситуацию с хэшами.
Сразу определю какую задачу применения хешей буду рассматривать — аутентификация пользователей. Не токены восстановления паролей, не аутентификация запросов, не что-то еще. Это также не статья про защиту канала передачи данных, так что комментарии по challenge-response и SSL неуместны!
Матчасть (короткая)
Hash = хеш функция — (свертка) функция однозначного отображения строки (любой длины) на конечное множество (строку заданной длины).
Само число (строка) хеш — результат вычисления хеш-функции над данными.
Существуют криптографические и некриптографические (классифицируются отдельно, к ним относятся, например, контрольные суммы) хеш-функции.
Для криптографических хэшей есть три дополнительных условия, которые отличают их от всех остальных:
- Необратимость: для заданного значения хеш-функции m должно быть вычислительно неосуществимо найти блок данных X, для которого H(X)=m.
- Стойкость к коллизиям первого рода: для заданного сообщения M должно быть вычислительно неосуществимо подобрать другое сообщение N, для которого H(N)=H(M).
- Стойкость к коллизиям второго рода: должно быть вычислительно неосуществимо подобрать пару сообщений ~(M, M'), имеющих одинаковый хеш
Подробнее — ru.wikipedia.org/wiki/%D0%A5%D0%B5%D1%88%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5
Вникать в тонкости криптографии прикладному разработчику не обязательно, достаточно запомнить какие хэш-функции (алгоритмы по названию) можно сейчас использовать, а какие уже нет. MD5 — уже нельзя, коллеги, — используйте bcrypt/scrypt.
В веб-приложениях, в числе прочего, хеш-функции используются для безопасного хранения секретов (паролей) в базе данных.
Именно хэш-функция становится вашим последним оплотом, если злоумышленник смог свести нападение к локальной атаке на систему аутентификации. Про онлайн атаки (перебор паролей НТТР запросами), может быть, кто-то еще напишет позже.
Ниже перечислены требования, которым ваш хеш в базе должен удовлетворять:
- стойкость к атакам перебора (прямой перебор и перебор по словарю)
- невозможность поиска одинаковых паролей разных пользователей по хешам
Для выполнения первого требования нужно использовать стойкие в настоящее время (а не в 90х годах!) хеш-функции.
Для выполнения второго — к паролю перед хешированием добавляется случайная строка (соль). Таким образом, у двух пользователей с паролем «123456» будут разные соли «соль1» и «соль2», а соответственно и хеш-функции от «123456соль1» и «123456соль2» в базе тоже будут разные.
Теперь немного про систему хранения — и соль и сам хеш хранятся в базе данных.
То есть получив доступ к СУБД, злоумышленник получает и значения хешей и соли.
Используйте локальный параметр!
Чтобы усложнить жизнь при атаке перебора следует дописать соль к паролю, а не наоборот (для людей, которые пишут слева направо, конечно).
Так как хеш-функция, как правило, вычисляется последовательно по строке (требования поточности алгоритма), то злоумышленнику при переборе «соленых» хешей, будет проще, когда подхешовое выражение начинается с соли.
Проще потому, что он (злоумышленник) может предвычислить заранее хеш(соль) и далее считать хеш(соль)+хеш(пароль) уже куда быстрее (практически с той же скоростью, что и просто хеш(пароль)). Для всех паролей, что он будет перебирать.
Для того чтобы еще усложнить жизнь атакующему, Solar Designer www.openwall.com/presentations/YaC2012-Password-Hashing-At-Scale/mgp00005.html предлагает ввести еще одну штуку, под названием локальный параметр.
Это по сути «вторая соль» дописывается ко всем (паролям+соль) конструкциям, и является одинаковой для всех хешей в базе. В чем же трюк? В том, что локального параметра в базе нет. Это константа системы, которая хранится в памяти приложения, куда она попадает из конфига (любым способом, только не из базы).
Очень простая и действенная мера, которая позволяет практически полностью исключить атаку перебора по данным только одного хранилища хешей (без знания локального параметра).
Единственный раз мы (ONsec) ломали хеши с локальным параметром, выработав при этом тактику атаки на сам локальный параметр (регистрируемся в приложении, затем ищем в базе свой хеш, соль (свой пароль мы и так знаем) и перебираем ЛП). И тщетно. На длинах 16+ байт для современных функций хеширования — это очень дорого по железу. В итоге проще оказалось скомпрометировать систему аутентификации (проставить себе role=admin в базе через UPDATE ;) )
Очень рекомендую ознакомиться с презентацией: www.openwall.com/presentations/YaC2012-Password-Hashing-At-Scale/mgp00001.html
Защищайте свои хранилища надежно и грамотно!
Заключение
Буду реалистом — естественно, никто не станет переписывать свои проекты ради «каких-то» хешей. Но новые проекты можно писать на scrypt/bcrypt. А также — внедряйте локальный параметр даже на слабых MD5 — он правда помогает, проверено :)
При переходе на другой тип хеширования, помимо трудозатрат, часто встает вопрос производительности. Действительно, более стойкие алгоритмы потребляют больше ресурсов. Тестируйтесь перед внедрением для своих нагрузок по скорости аутентификации пользователей в секунду (для большинства крупных проектов переход на scrypt оказался безболезненным). Выбор конкретного идеального типа хеша в конкретной ситуации может сильно разнится. Так, например, ДБО все чаще выбирают железные решения для генерации хешей с заданной скоростью.
В заключении, привожу скорости перебора хешей (единицы измерения — мегахэши в секунду, то есть количество ), полученных на карточке AMD Radeon 7990 стоимостью менее $1000 (даже по старому курсу):
- MD5: 16000 M/s
- SHA-1: 5900 M/s
- SHA256: 2050 M/s
- SHA512: 220 M/s
- NTLM: 28400 M/s
- bcrypt: 8,5 k/s
А по поводу эффективности перебора bcrypt рекомендую также ознакомиться с www.openwall.com/presentations/Passwords13-Energy-Efficient-Cracking/Passwords13-Energy-Efficient-Cracking.pdf