Обновите старые сохраненные пароли 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);

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

4 ответа

Решение

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

  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, которое должно быть обработано.

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

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

    BCrypt основан на блочном шифре Blowfish. Он берет алгоритм настройки расписания ключей из шифра и использует его для хеширования паролей. Причина, по которой он хорош, заключается в том, что алгоритм настройки 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 используются для шифрования (состояние или часть соли), которое было предварительно инициализировано.

    4. Так же

    5. Так же

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

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

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

Не гнездиться 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 функция. Он хорошо проверен экспертами, расширяем и совместим во многих приложениях.

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

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

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

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

Безопасный хэш и соль для паролей 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;
}
Другие вопросы по тегам