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

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

Я читал много сообщений здесь о crypt, md5, hash, 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;
    }
    
  • Как отметил 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 generate a new one
        $newHash = createBlowfishHash($password);
        saveUpdatedPasswordHash($newHash);
        return true;
    } else {
        return verifyBlowfishHash($password, $hash);
    }
}

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

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

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

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

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

    Кроме того,

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

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

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

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

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

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

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

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

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

    Это ключевой вывод из Blowfish Cipher. BCrypt модифицирует это:

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

    • То же

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

    • То же

    • То же

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

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

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

Ответ 4

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

Защитить хэш и соль для паролей 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;
}