Обновите старые сохраненные пароли MD5 в PHP для повышения безопасности - PullRequest
1 голос
/ 13 января 2012

На данный момент у меня есть база данных с паролями 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);

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

Ответы [ 4 ]

9 голосов
/ 13 января 2012

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

  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 бита).

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

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

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

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

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

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

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

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

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

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

    Это ключ, полученный из шифра Blowfish.BCrypt изменяет это так:

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

    2. То же самое

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

    4. То же самое

    5. То же самое

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

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

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

3 голосов
/ 13 января 2012

Для реализации вашего нового, более безопасного хранилища паролей следует использовать bcrypt или PBKDF2 , поскольку это действительно лучшее решение на данный момент.вкладывать вещи, так как вы не получаете никакой реальной безопасности из-за коллизий, как описывает @ Anony-Mousse.

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

3 голосов
/ 13 января 2012

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

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

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

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

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

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

1 голос
/ 13 января 2012

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

Безопасный хеш и соль для паролей 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;
}
...