Обновленный PHP: Защита паролей в современном PHP

Познакомьтесь с тем, каким образом в версии PHP 5.5 существенно повышена безопасность манипулирования паролями

Защитите пароли своих веб-приложений PHP от хакеров. Язык PHP продолжает эволюционировать вместе с веб-средой; при этом все и больше веб-приложений нуждаются в надежном хранении паролей. Во второй статье состоящего из четырех частей цикла Обновленный PHP описываются новые средства манипулирования паролями, добавленные в версии PHP 5.5.

Эли Уайт, технический директор, MojoLive

Эли Уайт (Eli White) – фотографияЭли Уайт (Eli White) работает над созданием Web-приложений уже более 16 лет; в настоящее время является техническим директором компании MojoLive. Последние 10 лет он занимается исключительно PHP-проектами. В прошлом работал в разных областях, включая проекты Zend, TripAdvisor, Digg и программу космического телескопа "Хаббл". Является автором книги "Практика использования PHP 5" и регулярно участвует в конференциях.



11.12.2015

С самого начала своего существования язык программирования PHP был предназначен для построения веб-сайтов. Эта идея встроена в PHP гораздо глубже, чем в любой другой язык программирования — возможно, это одна из причин, по которой язык PHP стал и остается столь популярным средством для создания веб-приложений. Однако в середине 1990-х годов, когда язык PHP разрабатывался, термин веб-приложение еще не существовал. В то время защита паролей не относилась к категории возможностей, на которые создатели PHP обращали внимание. В конце концов разработчику не нужно было волноваться о паролях, пока он использовал PHP только для того, чтобы поместить на своей веб-странице счетчик посещения сайта или метку даты изменения.

Однако прошло 20 лет – и сегодня нельзя даже подумать о создании веб-приложения, не имеющего защищенных паролем учетных записей пользователей. Исключительно важно, чтобы PHP-программисты оберегали пароли учетных записей с помощью самых современных и безопасных методов. С этой целью в версии PHP 5.5 была добавлена новая библиотека хеширования паролей, которую создал Энтони Феррара, (@ircmaxell). Эта библиотека предоставляет доступ к нескольким функциям, которые можно использовать для реализации одностороннего шифрования пароля с помощью современных наилучших методик. Другие функции предвосхищают будущие потребности безопасности, чтобы можно было оставаться на шаг впереди злоумышленников, когда компьютеры и хакерские технологии станут более совершенными. Эта статья предлагает читателю углубленное введение в функции этой библиотеки и описывает способы ее наилучшего использования.

Об этом цикле статей

Непрерывно эволюционируя в рамках активного проекта с открытым исходным кодом, язык PHP в настоящее время обеспечивает функционирование значительной части мирового Web. Современный PHP значительно отличается от того языка сопоставления с шаблонами, каким он был в момент своего зарождения. Если вы много лет не использовали и не изучали технологию PHP, то вы с трудом узнаете некоторые из сегодняшних проектов на PHP. Этот цикл статей демонстрирует новейшие возможности PHP и рассказывает, как использовать сегодняшний язык PHP для создания современных безопасных веб-сайтов.

Важность хешей для поддержания безопасности

Относитесь к своему паролю как к зубной щетке. Не позволяйте никому другому пользоваться им и заводите новый каждые шесть месяцев

Клиффорд Столл (Clifford Stoll)

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

Что такое хеш?

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

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

Скорость взламывания

Не все хеши равноценны. Для создания хеша можно использовать различные алгоритмы. В прошлом обычно использовались два алгоритма MD5 и SHA-1. Современные мощные компьютеры способны довольно легко взломать оба этих алгоритма. Например, существует программное обеспечение, которое при работе на одном графическом процессоре взламывает хеши со скоростью 3650 миллионов вычислений в секунду для алгоритма MD5 и 1360 миллионов вычислений в секунду для алгоритма SHA 1. При таких скоростях не очень сложные и длинные пароли можно взломать менее чем час.

Таким образом, большое значение имеет применение алгоритмов хеширования с более высокой вычислительной сложностью. Нам нужен не только длинный хеш (что уменьшает вероятность конфликтов хешей — когда две фразы дают один и тот же хеш), но медленно вычисляемый. Почему? Пользователь вашего веб-приложения должен ждать, пока сгенерируется хеш пароля, всего лишь один раз при каждом входе в систему. Если это ожидание длится секунду (или даже несколько секунд), пользователь не обратит внимания или вообще не заметит этого. Однако снижение скорости взлома с 3,6 миллиардов вычислений в секунду до 1 вычисления в секунду резко усложняет попытки взлома.

Радужные таблицы

Нам также необходимо защищаться от т. н. радужных таблиц (rainbow table). Радужные таблицы, например, таблица для алгоритма MD5, которую можно получить на сайте md5cracker.org, представляют собой таблицы обратного преобразования хешей. Создатель такой таблицы заранее вычисляет MD5-хеши для множества распространенных слов, фраз, измененных слов и даже случайных строк. Лицо, имеющее доступ к хешу, может ввести его в поисковый механизм, чтобы выявить пароль, который использовался для генерации этого хеша, то есть по существу инвертировать односторонний процесс. Относительно низкие вычислительные затраты на взламывание MD5-хеша обеспечивают возможность создания такой радужной таблицы.

Генерация радужной таблицы для алгоритма с высокой вычислительной сложностью занимает гораздо больше времени. Однако это по-прежнему возможно, поскольку трудности создателя являются однократными. Надлежащая контрмера состоит в добавлении к нашему хешу так называемой соли (salt). В этом контексте "соль" – это произвольная фраза, которая добавляется к паролю до создания хеша. Применение "соли" позволяет на практике обеспечивает победу над радужными таблицами. Теперь злоумышленнику нужно сгенерировать радужную таблицу специально для нашего приложения, а затем, на основе взлома нескольких паролей, выяснить, что представляет собой "соль" — а это трудный и дорогостоящий сценарий.


Совершенствование прежних PHP-практик в области паролей

В листинге 1 показано пример того, что несколько лет назад считалось хорошей практикой в сфере защиты паролей в PHP.

Листинг 1. Что было принято считать хорошей защитой паролей в PHP

Кликните, чтобы увидеть код

Листинг 1. Что было принято считать хорошей защитой паролей в PHP

 <?php // Создание класса Password для управления: class Password { const SALT = 'MyVoiceIsMyPassport'; public static function hash($password) { return hash('sha512', self::SALT . $password); } public static function verify($password, $hash) { return ($hash == self::hash($password)); } } // Хеширование пароля: $hash = Password::hash('correct horse battery staple'); // Проверка по введенному паролю (В этом примере верификация закончится неудачей) if (Password::verify('Tr0ub4dor&3', $hash)) { echo 'Correct Password!\n'; } else { echo "Incorrect login attempt!\n"; }

Читать:Документация по функции hash() на сайте php.net

Примерами, подобными листингу 1, под видом «наилучших методик» наводнен весь Интернет. И на протяжении долгого времени этот подход действительно был наилучшей методикой — он, несомненно, был лучше, чем использование алгоритма MD5, и радикально лучше, чем хранение паролей в виде простого текста. В листинге 1 используется гораздо более сложный алгоритм SHA 512; он принудительно применяет соль ко всем паролям, чтобы нейтрализовать заранее сделанные радужные таблицы. Однако при применении этого подхода остается несколько проблем.

Переход к случайной соли

В листинге 1 используется соль, однако все пароли используют абсолютно одинаковую соль. Соответственно, когда кто-то взломает хотя бы один пароль (или что, видимо, еще хуже, раскроет соль, получив доступ к базе кода), можно будет сделать специальную радужную таблицу посредством добавления соли к каждому элементу радужной таблицы. Решение для нейтрализации радужной таблицы состоит в том, чтобы использовать для каждого пароля случайную соль в момент его создания и хранить эту соль вместе с паролем таким способом, который позволял бы его извлечь.

Дальнейшее повышение вычислительных затрат

В листинге 1 также используется гораздо более сложный алгоритм SHA 512, который входит в комплект поставки PHP вместо алгоритмов MD5 и SHA 1. Тем не менее, даже хеши на основе SHA 512 можно взламывать со скоростью примерно 46 миллионов вычислений в секунду. Это медленнее, чем при использовании алгоритмов MD5 и SHA1, но тем не менее слишком быстро, чтобы обеспечить надлежащую защиту. Решение этой проблемы состоит в том, чтобы использовать алгоритмы с еще более высокой вычислительной сложностью и выполнять эти алгоритмы по несколько раз. Например, последовательная многократная (по 100 раз) обработка каждого пароля с помощью алгоритма SHA 512 значительно замедлила бы любую попытку взлома.

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


Представляем функцию password_hash()

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

 <?php $hash = password_hash('correct horse battery staple', PASSWORD_DEFAULT);

Задание стоимости вычислений

Вы также можете предоставить третий параметр, представляющий собой массив опций, которые изменяют порядок генерации хеша. Вы можете задать соль здесь, но лучше этого не делать, а разрешить генерацию случайной соли для вас. Что еще важнее, в этом массиве можно задать значение параметра cost. value. Это значение — равное по умолчанию 10 — определяет вычислительную сложность алгоритма и соответственно продолжительность генерации хеша (это значение можно рассматривать как количество повторных исполнений алгоритма "поверх себя" с целью замедления вычислений). Если вы хотите повысить безопасность пароля, а ваши аппаратные средства способны к этому, можно выполнить вызов следующим образом:

 <?php $hash = password_hash('correct horse battery staple', PASSWORD_DEFAULT, ['cost' => 14]);

При использовании моего компьютера MacBook Pro в качестве среды тестирования генерация хеша с помощью password_hash при значении cost, равном 10 (значение по умолчанию) занимает около 0,085 с. Повышение значения cost до 14 изменяет это время до серьезного значения: по 1,394 секунд в пересчете на одно вычисление.

Верификация сгенерированных паролей

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

 <?php if (password_verify($password, $hash)) { // Верный пароль }

Теперь осуществим рефакторинг класса в листинге 1, чтобы использовать встроенное расширение, хеширующее пароль (см. листинг 2).

Листинг 2. Рефакторинг класса Password из листинга 1

Кликните, чтобы увидеть код

Листинг 2. Рефакторинг класса Password из листинга 1

 <?php class Password { public static function hash($password) { return password_hash($password, PASSWORD_DEFAULT, ['cost' => 14]); } public static function verify($password, $hash) { return password_verify($password, $hash); } }

Читать:Документация по хешированию паролей на сайте php.net


Реагирование на меняющиеся потребности в области безопасности

С помощью нового расширения для хеширования паролей вы можете привести базу своего кода в соответствие с современными стандартами безопасности. Однако всего лишь несколько лет назад специалисты рекомендовали применять в качестве наилучшей методики алгоритм SHA-1. Что же произойдет, если — точнее, когда— потребуется более сильное шифрование паролей? К счастью, у нового расширения есть встроенная функция, которая учитывает эту возможность.

С помощью функции password_needs_rehash() можно определить — не афишируя это — что сохраненный пароль не соответствует текущим требованиям безопасности, которые вы установили. Причина может состоять в том, что вы увеличили параметр cost или что в новой версии PHP негласно осуществлен переход к другому алгоритму хеширования. Вот почему при выборе алгоритма вам следует указывать опцию PASSWORD_DEFAULT; в этом случае ваше программное обеспечение будет всегда использовать актуальную наилучшую методику.

Использование этой возможности несколько сложнее, но не чрезмерно. При проверке пароля пользователя (например, когда пользователь пытается войти в систему) вы выполняете одну дополнительную операцию: вызов функции password_needs_rehash(), которая принимает такие же параметры, как и функция password_hash(). Функция password_needs_rehash() проверяет хеш указанного пароля на соответствие новейшим требованиям к настройкам безопасности. Если хеш пароля не соответствует этим настройкам, функция сообщает вам об этом.

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

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

Листинг 3. Макет класса User, демонстрирующий полномасштабное использование расширения для хеширования паролей

Кликните, чтобы увидеть код

Листинг 3. Макет класса User, демонстрирующий полномасштабное использование расширения для хеширования паролей

 <?php class User { // Хранение опций пароля для совместного использования при хешировании и повторном хешировании: const HASH = PASSWORD_DEFAULT; const COST = 14; // Внутреннее хранение данных о пользователе: public $data; // Макет конструктора: public function __construct() { // Чтение данных из базы данных, сохранение данных в $data: // $data->passwordHash and $data->username $this->data = new stdClass(); $this->data->passwordHash = 'dbd014125a4bad51db85f27279f1040a'; } // Макет функциональности сохранения public function save() { // Сохранение данных из $data обратно в базу данных } // Разрешение изменения нового пароля: public function setPassword($password) { $this->data->passwordHash = password_hash($password, self::HASH, ['cost' => self::COST]); } // Логика для регистрации входа пользователя: public function login($password) { // Сначала проверка правильности введенного пользователем пароля: echo "Login: ", $this->data->passwordHash, "\n"; if (password_verify($password, $this->data->passwordHash)) { // Успех - теперь проверка пароля на необходимость повторного хеширования if (password_needs_rehash($this->data->passwordHash, self::HASH, ['cost' => self::COST])) { // Нам нужно повторно создать хеш пароля и сохранить его. Вызов setPassword $this->setPassword($password); $this->save(); } return true; // Или сделайте то, что вам нужно, чтобы пометить пользователя как успешно вошедшего в систему. } return false; } }

Заключение

Теперь вы знаете, как работает новая библиотека хеширования паролей для PHP и как она будет защищать ваших пользователей от нарушений безопасности. В следующей статье цикла Обновленный PHP я перейду от рассмотрения самого языка PHP к экосистеме и инструментам, которые начали развиваться на его основе — начиная с инструмента Composer для управления зависимостями в PHP, который быстро приобрел широкую популярность у PHP-сообщества.

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


  • Bluemix

    Узнайте больше информации о платформе IBM Bluemix, создавайте приложения, используя готовые решения!

  • Библиотека документов

    Более трех тысяч статей, обзоров, руководств и других полезных материалов.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Web-архитектура, Open source, Security
ArticleID=1023841
ArticleTitle=Обновленный PHP: Защита паролей в современном PHP
publish-date=12112015