Обновление старых сохраненных паролей md5 в PHP для повышения безопасности

На данный момент у меня есть база данных с сохраненными паролями md5, несколько лет назад это считалось немного более безопасным, чем сейчас, и это дошло до того, что пароли должны быть более безопасными.

Я читал много сообщений здесь о crypt , md5 , hash , bcrypt и т. Д., И bcrypt , чтобы использовать что-то в соответствии со следующими строками, чтобы «защитить» пароли лучше, чем они есть сейчас.

Я буду использовать комбинацию hash("sha512" и две соли, первая соль будет солью на сайте, хранящейся в файле, например .htaccess, и вторая соль будет создана для каждого пользователя.

Вот пример того, что я тестирую на данный момент:

.htaccess

 SetEnv SITEWIDE_SALT NeZa5Edabex?26Y#j5pr7VASpu$8UheVaREj$yA*59t*A$EdRUqer_prazepreTr 

example.php

 $currentpassword = //get password $pepper = getenv('SITEWIDE_SALT'); $salt = microtime().ip2long($_SERVER['REMOTE_ADDR']); $saltpepper = $salt.$pepper; $password = hash("sha512", md5($currentpassword).$saltpepper); 

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

Хорошо, давайте рассмотрим несколько пунктов здесь

  1. То, что у вас есть в $salt , не соль. Он детерминирован (это означает, что в нем нет случайности). Если вы хотите получить соль, используйте либо mcrypt_create_iv($size, MCRYPT_DEV_URANDOM) либо какой-либо другой источник фактической случайной энтропии. Дело в том, что оно должно быть как уникальным, так и случайным. Обратите внимание, что это не должно быть криптографически безопасным случайным … В абсолютном худшем случае я бы сделал что-то вроде этого:

     function getRandomBytes($length) { $bytes = ''; for ($i = 0; $i < $length; $i++) { $bytes .= chr(mt_rand(0, 255)); } return $bytes; } 
  2. Как указывал @ Anony-Mousse, никогда не подавайте вывод одной хеш-функции в другую, не добавляя к ней исходные данные. Вместо этого используйте правильный итеративный алгоритм, такой как PBKDF2 , PHPASS или CRYPT_BLOWFISH ($ 2a $).

    Мое предложение было бы использовать crypt с blowfish, так как это лучший доступный для PHP в это время:

     function createBlowfishHash($password) { $salt = to64(getRandomBytes(16)); $salt = '$2a$10$' . $salt; $result = crypt($password, $salt); } 

    И затем проверьте, используя такой метод:

     function verifyBlowfishHash($password, $hash) { return $hash == crypt($password, $hash); } 

    (обратите внимание, что to64 – хороший метод, определенный здесь ). Вы также можете использовать str_replace('+', '.', base64_encode($salt));

Я также предлагаю вам прочитать следующие два:

  • Фундаментальное различие между хэшированием и шифрованием
  • Многие хеш-итерации, каждый раз добавляйте соль?

Изменить: отвечать на вопрос о миграции

Хорошо, поэтому я понимаю, что мой ответ не касался аспекта миграции исходного вопроса. Итак, вот как я это разрешу.

Во-первых, создайте временную функцию для создания нового хэша blowfish из исходного хеша md5 со случайной солью и префиксом, чтобы мы могли обнаружить это позже:

 function migrateMD5Password($md5Hash) { $salt = to64(getRandomBytes(16)); $salt = '$2a$10$' . $salt; $hash = crypt($md5Hash, $salt); return '$md5' . $hash; } 

Теперь запустите все существующие хеши md5 через эту функцию и сохраните результат в базе данных. Мы помещаем свой собственный префикс, чтобы мы могли обнаружить исходный пароль и добавить дополнительный шаг md5. Итак, теперь мы все мигрировали.

Затем создайте еще одну функцию для проверки паролей и, если необходимо, обновите базу данных новым хешем:

 function checkAndMigrateHash($password, $hash) { if (substr($hash, 0, 4) == '$md5') { // Migrate! $hash = substr($hash, 4); if (!verifyBlowfishHash(md5($password), $hash) { return false; } // valid hash, so let's generate a new one $newHash = createBlowfishHash($password); saveUpdatedPasswordHash($newHash); return true; } else { return verifyBlowfishHash($password, $hash); } } 

Это то, что я хотел бы предложить по нескольким причинам:

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

Чтобы ответить на комментарии:

  1. Соль не обязательно должна быть случайной – я направляю вас в RFC 2898 – Криптография на основе паролей . А именно, раздел 4.1. И я цитирую:

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

    Дополнительно,

    Заметка. Если генератор случайных чисел или псевдослучайный генератор недоступен, детерминированной альтернативой для генерации соли (или ее случайной части) является применение функции деривации ключа на основе пароля для пароля и сообщения M, подлежащего обработке.

    Генератор PseudoRandom доступен, так почему бы не использовать его?

  2. Является ли ваше решение таким же, как bcrypt? Я не могу найти много документации о том, что такое bcrypt на самом деле? – Я предполагаю, что вы уже прочитали статью о Википедии bcrypt и попытаетесь объяснить ее лучше.

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

    1. Для установки расписания путем инициализации массивов с заданными статическими значениями используется массив из 18 элементов (называемый P-боксами, размером 32 бита) и 4 двумерными массивами (называемые S-блоками, каждый из которых содержит 256 записей по 8 бит каждый). Кроме того, 64-битное состояние инициализируется для всех 0.

    2. Ключ, пройденный, – XOred со всеми 18 P-боксами (поворот ключа, если он слишком короткий).

    3. Затем поля P используются для шифрования состояния, которое было ранее инициализировано.

    4. Зашифрованный текст, созданный на шаге 3, используется для замены P1 и P2 (первые 2 элемента массива P).

    5. Шаг 3 повторяется, и результат помещается в P3 и P4. Это продолжается до заполнения P17 и P18.

    Это ключевой вывод из Blowfish Cipher. BCrypt изменяет это на следующее:

    1. 64-разрядное состояние инициализируется зашифрованной версией соли .

    2. Одна и та же

    3. Затем P-поля используются для шифрования (состояния xor-части соли ), которое было ранее инициализировано.

    4. Одна и та же

    5. Одна и та же

    6. Полученная настройка затем используется для шифрования пароля 64 раза. Это то, что было возвращено BCrypt.

    Дело простое: это очень дорогостоящий алгоритм, который занимает много процессорного времени. Это настоящая причина, по которой его следует использовать.

Надеюсь, это прояснит ситуацию.

Реализация вашего нового, более безопасного хранилища паролей должна использовать bcrypt или PBKDF2 , так как это действительно лучшее решение прямо сейчас.

Не гнездайте вещи, так как вы не получаете никакой реальной безопасности из-за столкновений, как описывает @ Anony-Mousse.

Что вы можете сделать, это реализовать «переходную процедуру», когда ваше приложение переходит из старой системы на базе MD5 в новую более безопасную систему при входе в систему. Когда приходит запрос на вход, посмотрите, находится ли пользователь в новой, более безопасной системы. Если это так, bcrypt / PBKDF2 пароль, сравните, и вам хорошо идти. Если они не являются (никто не будет первым), проверьте их, используя более старую систему на базе MD5. Если он соответствует (пароль верен), выполните преобразование пароля bcrypt / PBKDF2 (так как теперь у вас есть), сохраните его в новой системе и удалите старую запись MD5. В следующий раз, когда они войдут в систему, у них есть запись в новой системе, поэтому вам будет хорошо. После того, как все пользователи войдут в систему, как только вы это реализуете, вы можете удалить эту функцию перехода и просто пройти аутентификацию против новой системы.

Не вставляйте md5 в свой хэш sha512 . Столкновение md5 затем подразумевает также хэш-столкновение во внешнем хеше (потому что вы хэшируете одни и те же значения!)

Общий способ хранения паролей – использовать схему, такую ​​как

 <method><separator><salt><separator><hash> 

При проверке пароля вы читаете <method> и <salt> из этого поля, повторно применяете их к паролю, а затем проверяете, что он создает тот же <hash> .

Проверьте доступные функции криптографии. В современной системе Linux crypt должен иметь возможность использовать sha512 пароля sha512 разумным способом: руководство PHP crypt . Не изобретайте велосипед , вы, вероятно, просто испортите больше, чем md5 , если только вы не специалист по криптографическому хешированию. Он даже позаботится о схеме выше: стандарт Linux должен использовать $ as separator, а $6$ – идентификатор метода для sha512 , а $2a$ означает, что вы хотите использовать blowfish . Таким образом, вы можете использовать несколько хэшей в своей базе данных. md5 имеют префикс $1$<salt>$ (если вы не заново изобрели хеширование md5, ваши хэши могут быть несовместимы).

Серьезно повторите использование существующей функции crypt . Он хорошо проверяется экспертами, расширяется и совместим во многих приложениях.

Я просмотрел эту тему некоторое время назад и нашел следующую полезную ссылку:

Безопасный хэш и соль для паролей PHP

Я также использую следующее, чтобы создать случайную соль:

 public static function getRandomString($length = 20) { $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; $string = ''; for ($i = 0; $i < $length; $i++) { $string .= substr($characters, (mt_rand() % strlen($characters)), 1); } return $string; }