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


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

Что мне нужно знать, так это:

  1. Как зашифровать и расшифровать пароль в PHP?

  2. Какой самый безопасный алгоритм шифрования паролей с помощью?

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

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

  5. Какими способами пароль может быть украден и расшифрован? О чем мне нужно знать?

Author: jww, 2011-02-23

8 answers

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

  1. Как зашифровать и расшифровать пароль в PHP?

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

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

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

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

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

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

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

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

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

  5. Какими способами пароль может быть украден и расшифрован? О чем мне нужно знать?

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

    Используйте SSL для всего трафика. И убедитесь, что на сервере нет никаких уязвимостей (CSRF, XSS, SQL-инъекция, Повышение привилегий, Удаленное выполнение кода и т.д.).

Редактировать: Вот реализация класса 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 во второй раз, чтобы показать вам, что разные экземпляры все равно будут правильно расшифровывать данные.

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

  1. Ключи

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

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

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

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

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

    • Он использует mcrypt для фактического выполнения шифрования. Я бы предложил использовать либо MCRYPT_BLOWFISH, либо MCRYPT_RIJNDAEL_128 шифры и 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: Я только что обновил этот класс с помощью ГОРАЗДО лучшего метода шифрования, используя лучшие производные ключи и исправляя поколение MAC...

 203
Author: ircmaxell, 2015-05-27 13:18:25

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

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

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

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

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

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

 14
Author: Ivan, 2011-02-23 11:49:22
  1. Функция PHP, которую вы ищете, - это Mcrypt (http://www.php.net/manual/en/intro.mcrypt.php).

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

<?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_декрипт чтобы расшифровать ваш пароль.

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

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

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

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

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

 12
Author: Jon Rhoades, 2013-12-22 04:44:05

Многие пользователи предлагали использовать mcrypt... что правильно, но я хотел бы сделать еще один шаг, чтобы его было легко хранить и передавать (так как иногда зашифрованные значения могут затруднять их отправку с использованием других технологий, таких как curl или 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);
 6
Author: Bradley, 2011-02-25 23:20:21

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

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

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

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

  1. Расширение Mcrypt
  2. AES-256, вероятно, является безопасной ставкой, но это может быть вопросом SO само по себе
  3. Вы этого не сделаете - это будет их пароль для приложения

Оба

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

5.

  • Если кто-то украдет данные приложения, он так же безопасен, как симметричный шифр (для схемы с открытым ключом он используется для защиты закрытого ключа с помощью парольной фразы.)
  • Ваше приложение определенно должно быть доступно только по протоколу SSL, предпочтительно с использованием клиентских сертификатов.
  • Рассмотрите возможность добавления второго фактора для аутентификации, который будет использоваться только один раз за сеанс, например, токен, отправленный по SMS.
 5
Author: Long Ears, 2011-02-23 11:57:33

Я пробовал что-то подобное, но, пожалуйста, обратите внимание, что я не криптограф и не обладаю глубокими знаниями о 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.

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

 2
Author: Santosh Linkha, 2011-03-01 04:29:28

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

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

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

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

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

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

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

 2
Author: symcbean, 2011-03-01 10:33:05

Используйте пароль_хаш и пароль_верифицировать

<?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.';
}
?>
 1
Author: jvitoroc, 2017-09-24 22:51:26