Двустороннее шифрование: мне нужно хранить пароли, которые можно получить

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

Мне нужно знать:

  • Как шифровать и расшифровывать пароль в PHP?

  • Какой самый безопасный алгоритм для шифрования паролей?

  • Где я могу хранить закрытый ключ?

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

  • Каким образом пароль может быть украден и дешифрован? Что мне нужно знать?

Ответ 1

Лично я использовал бы mcrypt, как и другие. Но есть еще много примечаний...

  • Как зашифровать и дешифровать пароль в PHP?

    Ниже приведен сильный класс, который заботится обо всем для вас:

  • Какой самый безопасный алгоритм для шифрования паролей?

    безопасный? любой из них. Самый безопасный метод, если вы собираетесь шифровать, - это защита от уязвимостей раскрытия информации (XSS, удаленное включение и т.д.). Если он выйдет, злоумышленник может в конечном итоге взломать шифрование (без шифрования на 100% не обратимо без ключа). Как указывает @NullUserException, это не совсем так. Существуют некоторые схемы шифрования, которые невозможно взломать, например OneTimePad).

  • Где я могу хранить закрытый ключ?

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

    $key = $userKey . $serverKey . $userSuppliedKey;
    

    Преимущество там в том, что любые 2 ключа могут быть скомпрометированы без ущерба для данных. Если есть атака SQL Injection, они могут получить $userKey, но не другие 2. Если есть локальный сервер, они могут получить $userKey и $serverKey, но не третий $userSuppliedKey. Если они будут бить пользователя с помощью гаечного ключа, они могут получить $userSuppliedKey, но не другие 2 (но опять же, если пользователь избит гаечным ключом, вы все равно слишком поздно).

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

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

  • Каким образом пароль может быть украден и дешифрован? Что мне нужно знать?

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

    Использовать SSL для всего трафика. И убедитесь, что на сервере нет каких-либо уязвимостей (CSRF, XSS, SQL Injection, Privilege Escalation, Remote Code Execution и т.д.).

Изменить: Здесь реализована реализация класса PHP с использованием надежного метода шифрования:

/**
 * A class to handle secure encryption and decryption of arbitrary data
 *
 * Note that this is not just straight encryption.  It also has a few other
 *  features in it to make the encrypted data far more secure.  Note that any
 *  other implementations used to decrypt data will have to do the same exact
 *  operations.  
 *
 * Security Benefits:
 *
 * - Uses Key stretching
 * - Hides the Initialization Vector
 * - Does HMAC verification of source data
 *
 */
class Encryption {

    /**
     * @var string $cipher The mcrypt cipher to use for this instance
     */
    protected $cipher = '';

    /**
     * @var int $mode The mcrypt cipher mode to use
     */
    protected $mode = '';

    /**
     * @var int $rounds The number of rounds to feed into PBKDF2 for key generation
     */
    protected $rounds = 100;

    /**
     * Constructor!
     *
     * @param string $cipher The MCRYPT_* cypher to use for this instance
     * @param int    $mode   The MCRYPT_MODE_* mode to use for this instance
     * @param int    $rounds The number of PBKDF2 rounds to do on the key
     */
    public function __construct($cipher, $mode, $rounds = 100) {
        $this->cipher = $cipher;
        $this->mode = $mode;
        $this->rounds = (int) $rounds;
    }

    /**
     * Decrypt the data with the provided key
     *
     * @param string $data The encrypted datat to decrypt
     * @param string $key  The key to use for decryption
     * 
     * @returns string|false The returned string if decryption is successful
     *                           false if it is not
     */
    public function decrypt($data, $key) {
        $salt = substr($data, 0, 128);
        $enc = substr($data, 128, -64);
        $mac = substr($data, -64);

        list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);

        if (!hash_equals(hash_hmac('sha512', $enc, $macKey, true), $mac)) {
             return false;
        }

        $dec = mcrypt_decrypt($this->cipher, $cipherKey, $enc, $this->mode, $iv);

        $data = $this->unpad($dec);

        return $data;
    }

    /**
     * Encrypt the supplied data using the supplied key
     * 
     * @param string $data The data to encrypt
     * @param string $key  The key to encrypt with
     *
     * @returns string The encrypted data
     */
    public function encrypt($data, $key) {
        $salt = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
        list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);

        $data = $this->pad($data);

        $enc = mcrypt_encrypt($this->cipher, $cipherKey, $data, $this->mode, $iv);

        $mac = hash_hmac('sha512', $enc, $macKey, true);
        return $salt . $enc . $mac;
    }

    /**
     * Generates a set of keys given a random salt and a master key
     *
     * @param string $salt A random string to change the keys each encryption
     * @param string $key  The supplied key to encrypt with
     *
     * @returns array An array of keys (a cipher key, a mac key, and a IV)
     */
    protected function getKeys($salt, $key) {
        $ivSize = mcrypt_get_iv_size($this->cipher, $this->mode);
        $keySize = mcrypt_get_key_size($this->cipher, $this->mode);
        $length = 2 * $keySize + $ivSize;

        $key = $this->pbkdf2('sha512', $key, $salt, $this->rounds, $length);

        $cipherKey = substr($key, 0, $keySize);
        $macKey = substr($key, $keySize, $keySize);
        $iv = substr($key, 2 * $keySize);
        return array($cipherKey, $macKey, $iv);
    }

    /**
     * Stretch the key using the PBKDF2 algorithm
     *
     * @see http://en.wikipedia.org/wiki/PBKDF2
     *
     * @param string $algo   The algorithm to use
     * @param string $key    The key to stretch
     * @param string $salt   A random salt
     * @param int    $rounds The number of rounds to derive
     * @param int    $length The length of the output key
     *
     * @returns string The derived key.
     */
    protected function pbkdf2($algo, $key, $salt, $rounds, $length) {
        $size   = strlen(hash($algo, '', true));
        $len    = ceil($length / $size);
        $result = '';
        for ($i = 1; $i <= $len; $i++) {
            $tmp = hash_hmac($algo, $salt . pack('N', $i), $key, true);
            $res = $tmp;
            for ($j = 1; $j < $rounds; $j++) {
                 $tmp  = hash_hmac($algo, $tmp, $key, true);
                 $res ^= $tmp;
            }
            $result .= $res;
        }
        return substr($result, 0, $length);
    }

    protected function pad($data) {
        $length = mcrypt_get_block_size($this->cipher, $this->mode);
        $padAmount = $length - strlen($data) % $length;
        if ($padAmount == 0) {
            $padAmount = $length;
        }
        return $data . str_repeat(chr($padAmount), $padAmount);
    }

    protected function unpad($data) {
        $length = mcrypt_get_block_size($this->cipher, $this->mode);
        $last = ord($data[strlen($data) - 1]);
        if ($last > $length) return false;
        if (substr($data, -1 * $last) !== str_repeat(chr($last), $last)) {
            return false;
        }
        return substr($data, 0, -1 * $last);
    }
}

Обратите внимание, что я использую функцию, добавленную в PHP 5.6: hash_equals. Если вы находитесь на уровне ниже 5.6, вы можете использовать эту функцию-заменитель, которая реализует сравнение времени с безопасностью с помощью двойная проверка HMAC:

function hash_equals($a, $b) {
    $key = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
    return hash_hmac('sha512', $a, $key) === hash_hmac('sha512', $b, $key);
}

Использование:

$e = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$encryptedData = $e->encrypt($data, $key);

Затем, чтобы расшифровать:

$e2 = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$data = $e2->decrypt($encryptedData, $key);

Обратите внимание, что я использовал $e2 второй раз, чтобы показать вам, что разные экземпляры все равно будут правильно расшифровывать данные.

Теперь, как это работает/зачем использовать его над другим решением:

  • Клавиши

    • Ключи не используются напрямую. Вместо этого ключ растягивается стандартным выводом PBKDF2.

    • Ключ, используемый для шифрования, уникален для каждого зашифрованного блока текста. Таким образом, поставляемый ключ становится "основным ключом". Таким образом, этот класс обеспечивает ключевое вращение для ключей шифрования и auth.

    • ВАЖНОЕ ПРИМЕЧАНИЕ, параметр $rounds настроен для истинных случайных ключей достаточной силы (128 бит криптографически безопасного случайного как минимум). Если вы собираетесь использовать пароль или неслучайный ключ (или менее случайные, а затем 128 бит случайного CS), вы должны увеличивать этот параметр. Я бы предложил минимум 10000 для паролей (чем больше вы можете позволить себе, тем лучше, но это добавит во время выполнения)...

  • Целостность данных

    • В обновленной версии используется ENCRYPT-THEN-MAC, что является гораздо лучшим способом обеспечения подлинности зашифрованных данных.
  • Шифрование:

    • Он использует mcrypt для фактического выполнения шифрования. Я бы предложил использовать для MCRYPT_BLOWFISH или MCRYPT_RIJNDAEL_128 cyphers и MCRYPT_MODE_CBC. Он достаточно прочен и по-прежнему довольно быстрый (цикл шифрования и дешифрования занимает около 1/2 секунды на моей машине).

Теперь, что касается пункта 3 из первого списка, то, что вам даст, - это такая функция:

function makeKey($userKey, $serverKey, $userSuppliedKey) {
    $key = hash_hmac('sha512', $userKey, $serverKey);
    $key = hash_hmac('sha512', $key, $userSuppliedKey);
    return $key;
}

Вы можете растянуть его в функции makeKey(), но поскольку это будет растянуто позже, на самом деле это не очень важно.

Что касается размера хранилища, это зависит от обычного текста. Blowfish использует размер блока размером 8 байтов, поэтому у вас будет:

  • 16 байт для соли
  • 64 байта для hmac
  • длина данных
  • Заполнение, чтобы длина данных% 8 == 0

Итак, для 16-символьного источника данных будет зашифровано 16 символов данных. Таким образом, фактический размер зашифрованных данных составляет 16 байтов из-за заполнения. Затем добавьте 16 байт для соли и 64 байта для hmac, а общий размер хранится в 96 байт. Таким образом, в лучшем случае на 80 персонажей накладные расходы, а в худшем случае накладные 87 символов...

Я надеюсь, что это поможет...

Примечание: 12/11/12: Я только что обновил этот класс с помощью лучшего метода шифрования MUCH, используя лучшие производные ключи и исправляя генерацию MAC...

Ответ 2

Как шифровать и дешифровать пароль в PHP? Используя один из многих алгоритмов шифрования. (или используя одну из многих библиотек)

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

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

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

Каким образом пароль может быть украден и дешифрован? Что мне нужно знать? Это зависит от используемого алгоритма. Однако всегда убедитесь, что вы не отправляете пароль, не зашифрованный для пользователя или от него. Либо шифруйте/расшифруйте его на стороне клиента, либо используйте https (или другие криптографические средства для защиты соединения между сервером и клиентом).

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

Ответ 3

Пример из руководства слегка изменен для этого примера):

<?php
$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = "This is a very secret key";
$pass = "PasswordHere";
echo strlen($pass) . "\n";

$crypttext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $pass, MCRYPT_MODE_ECB, $iv);
echo strlen($crypttext) . "\n";
?>

Вы можете использовать mcrypt_decrypt, чтобы расшифровать ваш пароль.

  1. Лучший алгоритм довольно субъективен - попросите 5 человек, получите 5 ответов. Лично, если по умолчанию (Blowfish) недостаточно для вас, у вас, вероятно, больше проблем!

  2. Учитывая, что PHP необходим для шифрования - не уверен, что вы можете спрятать его в любом месте - приветствуйте его. Разумеется, стандартные методы кодирования PHP наилучшим образом!

  3. Учитывая, что ключ шифрования будет в вашем коде в любом случае, не уверен, что вы выиграете, предоставляя остальную часть вашего приложения.

  4. Очевидно, что если зашифрованный пароль и ключ шифрования украдены, то игра закончится.

Я отвечу гонщика на мой ответ - я не эксперт по криптографии PHP, но я думаю, что я ответил, это стандартная практика - я приветствую комментарии, которые могут иметь другие.

Ответ 4

Многие пользователи предложили использовать mcrypt... что правильно, но мне нравится идти дальше, чтобы сделать его легко сохраненным и перенесенным (поскольку иногда зашифрованные значения могут затруднить их передачу с использованием других технологий, таких как завиток, или json).

После того, как вы успешно зашифровали с помощью mcrypt, запустите его через base64_encode и затем преобразуйте его в шестнадцатеричный код. Один раз в шестнадцатеричном коде легко переносить различными способами.

$td = mcrypt_module_open('tripledes', '', 'ecb', '');
$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
$key = substr("SUPERSECRETKEY",0,mcrypt_enc_get_key_size($td));
mcrypt_generic_init($td, $key, $iv);
$encrypted = mcrypt_generic($td, $unencrypted);
$encrypted = $ua."||||".$iv;
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$encrypted = base64_encode($encrypted);
$encrypted = array_shift(unpack('H*', $encrypted));

И с другой стороны:

$encrypted = pack('H*', $encrypted);
$encrypted = base64_decode($encrypted);
list($encrypted,$iv) = explode("||||",$encrypted,2);
$td = mcrypt_module_open('tripledes', '', 'ecb', '');
$key = substr("SUPERSECRETKEY",0,mcrypt_enc_get_key_size($td));
mcrypt_generic_init($td, $key, $iv);
$unencrypted = mdecrypt_generic($td, $encrypted);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);

Ответ 5

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

Открытый ключ

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

Симметричные

  • Расширение Mcrypt
  • AES-256, вероятно, является безопасной ставкой, но это может быть вопрос SO сам по себе
  • У вас нет - это будет их пароль к приложению

И

4. Да - пользователи должны будут каждый раз вводить пароль своего приложения, но сохранение его в сеансе вызовет другие проблемы.

5.

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

Ответ 6

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

$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH , MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = "evenifyouaccessmydatabaseyouwillneverfindmyemail";
$text = "[email protected]";
echo "Key : ".$key."<br/>";
echo "Text : ".$text . "<br/>";
echo "Md5 : ".md5($text). "<br/>";
echo "Sha1 : ".sha1($text). "<br/>";



$crypttext = mcrypt_encrypt(MCRYPT_BLOWFISH , $key, $text, MCRYPT_MODE_ECB, $iv);
echo "Crypted Data : ".$crypttext."<br>";

$base64 = base64_encode($crypttext);
echo "Encoded Data : ".$base64."<br/>";
$decode =  base64_decode($base64);


$decryptdata = mcrypt_decrypt(MCRYPT_BLOWFISH , $key, $crypttext, MCRYPT_MODE_ECB, $iv);

echo "Decoded Data : ".ereg_replace("?", null ,  $decryptdata); 
//event if i add '?' to the sting to the text it works, I don't know why.

Обратите внимание, что это просто концепция. Любое улучшение этого кода было бы весьма значительным.

Ответ 7

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

А? Я не понимаю. Вы просто имеете в виду, что пароль должен быть восстановлен?

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

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

Сказав это, возможно построить разумно защищенную систему.

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

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

Ответ 8

Используйте password_hash и password_verify

<?php
/**
 * In this case, we want to increase the default cost for BCRYPT to 12.
 * Note that we also switched to BCRYPT, which will always be 60 characters.
 */
$options = [
    'cost' => 12,
];
echo password_hash("rasmuslerdorf", PASSWORD_BCRYPT, $options)."\n";
?>

И расшифровать:

<?php
// See the password_hash() example to see where this came from.
$hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';

if (password_verify('rasmuslerdorf', $hash)) {
    echo 'Password is valid!';
} else {
    echo 'Invalid password.';
}
?>