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

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

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

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

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

  3. где я храню частное ключ?

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

  5. каким образом можно украсть и расшифровать пароль? Что мне нужно знать?

8 ответов


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

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

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

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

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

  3. где я храню закрытый ключ?

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

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

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

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

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

  5. в что как можно украсть и расшифровать пароль? Что мне нужно знать?

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

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

Edit: вот реализация класса 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.

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

    • ВАЖНОЕ ПРИМЕЧАНИЕ, the $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...


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

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

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

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

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

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


  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_decrypt для расшифровки пароля.

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

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

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

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

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


многие пользователи предложили использовать 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);

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

публичный ключ

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

симметричного

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

и

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

5.

  • если кто-то крадет данные приложения, это так же безопасно, как симметричный шифр (для схемы открытого ключа он используется для защиты закрытого ключа с парольной фразой.)
  • ваше приложение должно быть доступно только через SSL, предпочтительно с использованием клиентских сертификатов.
  • рассмотреть вопрос о добавлении второго фактора аутентификации, который будет использоваться только один раз за сеанс, как маркер отправлено через СМС-СООБЩЕНИЕ.

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

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


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

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

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

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

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

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

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


использовать функция 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 = 'y$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';

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