Простейшее двустороннее шифрование с использованием PHP

Каков самый простой способ двустороннего шифрования в обычных установках PHP?

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

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


person user1206970    schedule 13.02.2012    source источник
comment
Просто выполните XOR для вашей строки.   -  person Thunraz    schedule 13.02.2012
comment
Для шифрования общего назначения используйте defuse / php-encryption / вместо собственного.   -  person Scott Arciszewski    schedule 11.05.2015
comment
Держитесь подальше от github.com/defuse/php-encryption - он медленнее на порядки величина, чем mcrypt.   -  person Eugen Rieck    schedule 11.05.2015
comment
defuse/php-encryption использует openssl(), который может использовать AES-NI (чего нет в libmcrypt) и, в зависимости от того, сколько данных вы шифруете, может обеспечить гораздо более высокую пропускную способность шифрования, чем mcrypt. Пожалуйста, опубликуйте сравнительный тест, чтобы подтвердить свое утверждение.   -  person Scott Arciszewski    schedule 11.05.2015
comment
Кроме того, @EugenRieck, шифрование, вероятно, не будет узким местом вашего приложения. Если это так, используйте libsodium;)   -  person Scott Arciszewski    schedule 12.05.2015
comment
@ Скотт. Размышления об этом, вероятно, не будут узким местом - вот что принесло нам много плохого программного обеспечения.   -  person Eugen Rieck    schedule 12.05.2015
comment
Если вы действительно шифруете / дешифруете много данных до такой степени, что миллисекунды, которые это стоит, останавливают ваше приложение, прикусите пулю и переключитесь на libsodium. Sodium::crypto_secretbox() и Sodium::crypto_secretbox_open() безопасны и производительны.   -  person Scott Arciszewski    schedule 12.05.2015
comment
С момента публикации моего предыдущего комментария привязки PHP libsodium изменились. Теперь вы хотите использовать \Sodium\crypto_secretbox() и \Sodium\crypto_secretbox_open().   -  person Scott Arciszewski    schedule 07.10.2015
comment
Я предлагаю вам использовать новую библиотеку Sodium в PHP 7.1+. Для этой библиотеки требуется только секретный и открытый ключ в качестве настройки, а для каждой строки - уникальный одноразовый номер. Эта (оболочка) библиотека может помочь вам в этом, методы просты в использовании, поэтому вам не нужно самостоятельно разбираться в библиотеке Sodium: github.com/internetpixels/sodium-encryption   -  person Petervw    schedule 17.06.2018
comment
rot13 с нулевым кодом доступа, imho   -  person Agnius Vasiliauskas    schedule 14.12.2018
comment
Да, я ищу это.   -  person Jovylle Bermudez    schedule 08.07.2020


Ответы (6)


Отредактировано:

Вам действительно стоит использовать openssl_encrypt () и openssl_decrypt ()

Как говорит Скотт, Mcrypt не является хорошей идеей, поскольку он не обновлялся с 2007 года.

Существует даже RFC для удаления Mcrypt из PHP - https://wiki.php.net/rfc/mcrypt-viking-funeral

person 472084    schedule 13.02.2012
comment
@EugenRieck Да, в том-то и дело. Mcrypt не получает исправлений. OpenSSL получает исправления при обнаружении любой уязвимости, большой или маленькой. - person Greg; 07.09.2016
comment
Было бы лучше, если бы такой ответ с большим количеством голосов был предоставлен в ответ также и на простейших примерах. Спасибо, в любом случае. - person T.Todua; 18.01.2018
comment
ребята, к вашему сведению = ›MCRYPT УСТАРЕЛ. capsing, поэтому каждый должен знать, что его нельзя использовать, так как это дало нам множество проблем. Если я не ошибаюсь, он устарел с PHP 7.1. - person clusterBuddy; 03.06.2019
comment
Начиная с PHP 7 функция mcrypt удалена из кодовой базы php. Поэтому при использовании последней версии php (которая должна быть стандартной) вы больше не можете использовать эту устаревшую функцию. - person Alexander Behling; 12.09.2019
comment
Вы также должны упомянуть, что Mcrypt устарел с PHP 7.1.0 и удален с PHP 7.2.0. - person Jonathan J. Pecany; 09.09.2020

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

Быть информированным. Проектируйте безопасные системы.

Переносимое шифрование данных в PHP

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

Если ваши цели переносимости не препятствуют требованию расширений PECL, libsodium будет < em> очень рекомендуется по сравнению со всем, что вы или я могу написать на PHP.

Обновление (2016-06-12): теперь вы можете использовать odium_compat и использовать тот же крипто-libsodium предлагает без установки расширений PECL.

Если вы хотите попробовать свои силы в криптографической инженерии, читайте дальше.


Во-первых, вы должны найти время, чтобы узнать об опасностях неаутентифицированного шифрования и Cryptographic Doom Principle.

  • Зашифрованные данные все еще могут быть изменены злоумышленником.
  • Аутентификация зашифрованных данных предотвращает подделку.
  • Аутентификация незашифрованных данных не предотвращает подделку.

Шифрование и дешифрование

Шифрование в PHP на самом деле простое (мы собираемся использовать openssl_encrypt() и _ 2_ после того, как вы примете решение о том, как зашифровать свою информацию. Обратитесь к openssl_get_cipher_methods() для получения списка методов, поддерживаемых в вашей системе. Лучшим выбором является AES в режиме CTR:

  • aes-128-ctr
  • aes-192-ctr
  • aes-256-ctr

В настоящее время нет причин полагать, что размер ключа AES является серьезной проблемой для беспокоиться (больше, вероятно, не лучше из-за плохого планирования ключей в 256-битном режиме).

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

Простая оболочка шифрования / дешифрования с использованием OpenSSL

class UnsafeCrypto
{
    const METHOD = 'aes-256-ctr';

    /**
     * Encrypts (but does not authenticate) a message
     * 
     * @param string $message - plaintext message
     * @param string $key - encryption key (raw binary expected)
     * @param boolean $encode - set to TRUE to return a base64-encoded 
     * @return string (raw binary)
     */
    public static function encrypt($message, $key, $encode = false)
    {
        $nonceSize = openssl_cipher_iv_length(self::METHOD);
        $nonce = openssl_random_pseudo_bytes($nonceSize);

        $ciphertext = openssl_encrypt(
            $message,
            self::METHOD,
            $key,
            OPENSSL_RAW_DATA,
            $nonce
        );

        // Now let's pack the IV and the ciphertext together
        // Naively, we can just concatenate
        if ($encode) {
            return base64_encode($nonce.$ciphertext);
        }
        return $nonce.$ciphertext;
    }

    /**
     * Decrypts (but does not verify) a message
     * 
     * @param string $message - ciphertext message
     * @param string $key - encryption key (raw binary expected)
     * @param boolean $encoded - are we expecting an encoded string?
     * @return string
     */
    public static function decrypt($message, $key, $encoded = false)
    {
        if ($encoded) {
            $message = base64_decode($message, true);
            if ($message === false) {
                throw new Exception('Encryption failure');
            }
        }

        $nonceSize = openssl_cipher_iv_length(self::METHOD);
        $nonce = mb_substr($message, 0, $nonceSize, '8bit');
        $ciphertext = mb_substr($message, $nonceSize, null, '8bit');

        $plaintext = openssl_decrypt(
            $ciphertext,
            self::METHOD,
            $key,
            OPENSSL_RAW_DATA,
            $nonce
        );

        return $plaintext;
    }
}

Пример использования

$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');

$encrypted = UnsafeCrypto::encrypt($message, $key);
$decrypted = UnsafeCrypto::decrypt($encrypted, $key);

var_dump($encrypted, $decrypted);

Демо: https://3v4l.org/jl7qR


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

Примечание. По умолчанию UnsafeCrypto::encrypt() возвращает необработанную двоичную строку. Назовите это так, если вам нужно сохранить его в безопасном двоичном формате (в кодировке base64):

$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');

$encrypted = UnsafeCrypto::encrypt($message, $key, true);
$decrypted = UnsafeCrypto::decrypt($encrypted, $key, true);

var_dump($encrypted, $decrypted);

Демо: http://3v4l.org/f5K93

Простая оболочка аутентификации

class SaferCrypto extends UnsafeCrypto
{
    const HASH_ALGO = 'sha256';

    /**
     * Encrypts then MACs a message
     * 
     * @param string $message - plaintext message
     * @param string $key - encryption key (raw binary expected)
     * @param boolean $encode - set to TRUE to return a base64-encoded string
     * @return string (raw binary)
     */
    public static function encrypt($message, $key, $encode = false)
    {
        list($encKey, $authKey) = self::splitKeys($key);

        // Pass to UnsafeCrypto::encrypt
        $ciphertext = parent::encrypt($message, $encKey);

        // Calculate a MAC of the IV and ciphertext
        $mac = hash_hmac(self::HASH_ALGO, $ciphertext, $authKey, true);

        if ($encode) {
            return base64_encode($mac.$ciphertext);
        }
        // Prepend MAC to the ciphertext and return to caller
        return $mac.$ciphertext;
    }

    /**
     * Decrypts a message (after verifying integrity)
     * 
     * @param string $message - ciphertext message
     * @param string $key - encryption key (raw binary expected)
     * @param boolean $encoded - are we expecting an encoded string?
     * @return string (raw binary)
     */
    public static function decrypt($message, $key, $encoded = false)
    {
        list($encKey, $authKey) = self::splitKeys($key);
        if ($encoded) {
            $message = base64_decode($message, true);
            if ($message === false) {
                throw new Exception('Encryption failure');
            }
        }

        // Hash Size -- in case HASH_ALGO is changed
        $hs = mb_strlen(hash(self::HASH_ALGO, '', true), '8bit');
        $mac = mb_substr($message, 0, $hs, '8bit');

        $ciphertext = mb_substr($message, $hs, null, '8bit');

        $calculated = hash_hmac(
            self::HASH_ALGO,
            $ciphertext,
            $authKey,
            true
        );

        if (!self::hashEquals($mac, $calculated)) {
            throw new Exception('Encryption failure');
        }

        // Pass to UnsafeCrypto::decrypt
        $plaintext = parent::decrypt($ciphertext, $encKey);

        return $plaintext;
    }

    /**
     * Splits a key into two separate keys; one for encryption
     * and the other for authenticaiton
     * 
     * @param string $masterKey (raw binary)
     * @return array (two raw binary strings)
     */
    protected static function splitKeys($masterKey)
    {
        // You really want to implement HKDF here instead!
        return [
            hash_hmac(self::HASH_ALGO, 'ENCRYPTION', $masterKey, true),
            hash_hmac(self::HASH_ALGO, 'AUTHENTICATION', $masterKey, true)
        ];
    }

    /**
     * Compare two strings without leaking timing information
     * 
     * @param string $a
     * @param string $b
     * @ref https://paragonie.com/b/WS1DLx6BnpsdaVQW
     * @return boolean
     */
    protected static function hashEquals($a, $b)
    {
        if (function_exists('hash_equals')) {
            return hash_equals($a, $b);
        }
        $nonce = openssl_random_pseudo_bytes(32);
        return hash_hmac(self::HASH_ALGO, $a, $nonce) === hash_hmac(self::HASH_ALGO, $b, $nonce);
    }
}

Пример использования

$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');

$encrypted = SaferCrypto::encrypt($message, $key);
$decrypted = SaferCrypto::decrypt($encrypted, $key);

var_dump($encrypted, $decrypted);

Демоверсии: необработанный двоичный файл, в кодировке base64


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

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

person Scott Arciszewski    schedule 12.05.2015
comment
Итак, я просто пытаюсь сначала заставить работать UnsafeCrypto. Шифрование происходит нормально, но каждый раз, когда я запускаю расшифровку, я получаю ответ «false». Я использую тот же ключ для дешифрования и передаю true как при кодировании, так и при декодировании. Я предполагаю, что в этом примере есть типо, мне интересно, откуда взялась моя проблема. Можете ли вы объяснить, откуда берется переменная $ mac, и должно ли это быть просто $ iv? - person David C; 07.07.2015
comment
@DavidC Да, должно. Извините, что не заметил этого раньше. Я также добавил 3v4l.org демонстрации кода примера. - person Scott Arciszewski; 21.07.2015
comment
@EugenRieck Реализации шифров OpenSSL, вероятно, единственные не отстойные части, и это единственный способ использовать AES-NI в обычном PHP. Если вы устанавливаете на OpenBSD, PHP будет скомпилирован с использованием LibreSSL, при этом код PHP не заметит разницы. Libsodium ›OpenSSL в любой день. Кроме того, не используйте libmcrypt. Что вы порекомендуете разработчикам PHP использовать вместо OpenSSL? - person Scott Arciszewski; 22.07.2015
comment
@ScottArciszewski - Вы вполне смогли прочитать мою рекомендацию, когда вы ее проголосовали против: мы все здесь взрослые, не нужно играть в игры. EOD, давайте просто соглашаемся не соглашаться. - person Eugen Rieck; 09.08.2015
comment
поддерживается только в PHP 5.3 и выше php.net/manual/en/function.openssl -encrypt.php любой вариант для более низкой версии, предпочтительно 5.2, но зашифрованный шифр должен быть чистым текстом? - person Akshay Khale; 06.09.2015
comment
Ни 5.2, ни 5.3 больше не поддерживаются. Вместо этого вам следует подумать об обновлении до поддерживаемой версии PHP, такой как 5.6. - person Scott Arciszewski; 06.09.2015
comment
Предложенный вами метод шифрования недоступен в моей системе. Как узнать, какие альтернативы безопасны? Например, на моем сервере существует буквально 17 различных вариантов AES. - person Abhi Beckert; 02.07.2016
comment
(aes-*-cbc или aes-*-ctr), тогда HMAC-SHA256 в порядке. aes-*-gcm лучше, но не доступен до PHP 7.1. - person Scott Arciszewski; 02.07.2016
comment
@ScottArciszewski, я опубликовал порт C # .NET для ваших очень полезных классов на GitHub: github.com/mayerwin/SaferCrypto - person Erwin Mayer; 03.01.2017
comment
Класс UnsafeCrypto неправильно работает с URL-адресами. Как мы можем заставить его создавать зашифрованные строки, готовые для использования в URL-адресах? - person Az.Youness; 06.04.2017
comment
@BBeta paragonie.com/blog/2015/ 09 / - person Scott Arciszewski; 06.04.2017
comment
Почему вы сделали эту строчку hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f')? какие были бы риски, если бы я вместо этого использовал ключ напрямую? Большое спасибо за то, что поделились своим опытом. - person Accountant م; 26.08.2017
comment
Я сделал это просто для демонстрации вам нужны двоичные строки, а не строки, читаемые человеком, для ваших ключей. - person Scott Arciszewski; 26.08.2017
comment
Отлично ... Но почему бы вам не объединить их в один класс, например: pastebin.com/sDtEtgJm - person T.Todua; 18.01.2018
comment
Что ж, целью примера кода была демонстрация, а не включение программирования с копированием и вставкой. - person Scott Arciszewski; 19.01.2018
comment
@ScottArciszewski Мне очень понравились ваши сообщения, и я также нашел github.com/paragonie/halite. Мне интересно, есть ли у вас мысли по поводу stackoverflow.com/q/51791514/470749 Почему соль должна быть случайной? в 1_? Означает ли это, что симметричное шифрование невозможно? Мне кажется, то, о чем я спрашиваю в этом посте, очень просто, и я удивлен, что не нашел ответа после целого дня поиска. Спасибо. - person Ryan; 10.08.2018
comment
Это так много, я умираю - person Jovylle Bermudez; 08.07.2020

Используйте mcrypt_encrypt() и _ 2_ с соответствующими параметрами. Действительно просто и понятно, и вы используете проверенный в бою пакет шифрования.

ИЗМЕНИТЬ

Через 5 лет и 4 месяца после этого ответа расширение mcrypt сейчас находится в процессе прекращения поддержки и, в конечном итоге, удаления из PHP.

person Eugen Rieck    schedule 13.02.2012
comment
Боевые испытания и не обновлялись более 8 лет? - person Maarten Bodewes; 09.10.2014
comment
Что ж, mcrypt находится в PHP7 и не является устаревшим - для меня этого достаточно. Не весь код имеет ужасное качество OpenSSL и требует исправления каждые несколько дней. - person Eugen Rieck; 11.05.2016
comment
mcrypt не просто ужасен в плане поддержки. Он также не реализует передовые практики, такие как заполнение, совместимое с PKCS # 7, аутентифицированное шифрование. Он не будет поддерживать SHA-3 или какой-либо другой новый алгоритм, поскольку его никто не поддерживает, что лишает вас возможности обновления. Кроме того, раньше он принимал такие вещи, как частичные ключи, выполнение нулевого заполнения и т. Д. Есть веская причина, по которой он постепенно удаляется из PHP. - person Maarten Bodewes; 12.05.2016
comment
В PHP 7.1 все функции mcrypt_ * будут вызывать уведомление E_DEPRECATED. В PHP 7.1 + 1 (будь то 7.2 или 8.0) расширение mcrypt будет перемещено из ядра в PECL, где люди, которые действительно хотят его установить, все равно могут это сделать, если они смогут установить PHP. расширения от PECL. - person Mladen Janjetovic; 20.05.2016

Шифрование с использованием openssl_encrypt () Функция openssl_encrypt обеспечивает безопасный и простой способ шифрования ваших данных.

В приведенном ниже сценарии мы используем метод шифрования AES128, но вы можете рассмотреть другой метод шифрования в зависимости от того, что вы хотите зашифровать.

<?php
$message_to_encrypt = "Yoroshikune";
$secret_key = "my-secret-key";
$method = "aes128";
$iv_length = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($iv_length);

$encrypted_message = openssl_encrypt($message_to_encrypt, $method, $secret_key, 0, $iv);

echo $encrypted_message;
?>

Вот объяснение используемых переменных:

message_to_encrypt: данные, которые вы хотите зашифровать secret_key: это ваш «пароль» для шифрования. Не выбирайте что-то слишком простое и будьте осторожны, чтобы не поделиться своим секретным ключом с другим методом: методом шифрования. Здесь мы выбрали AES128. iv_length и iv: подготовить шифрование, используя байты encrypted_message: переменная, включающая ваше зашифрованное сообщение

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

<?php
$message_to_encrypt = "Yoroshikune";
$secret_key = "my-secret-key";
$method = "aes128";
$iv_length = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($iv_length);
$encrypted_message = openssl_encrypt($message_to_encrypt, $method, $secret_key, 0, $iv);

$decrypted_message = openssl_decrypt($encrypted_message, $method, $secret_key, 0, $iv);

echo $decrypted_message;
?>

Метод дешифрования, предложенный openssl_decrypt (), близок к openssl_encrypt ().

Единственное отличие состоит в том, что вместо добавления $ message_to_encrypt вам нужно будет добавить уже зашифрованное сообщение в качестве первого аргумента openssl_decrypt ().

Это все, что вам нужно сделать.

person Harshal Lonare    schedule 24.08.2020
comment
Если я не прочитал должным образом, я думаю, стоит отметить, что секретный ключ и iv необходимо сохранить, если вы хотите расшифровать позже. Я не мог начать работу, пока не понял это, прочитав эту ссылку php.net/manual/en/function.openssl-encrypt.php#example-903 - person gstlouis; 03.06.2021

PHP 7.2 полностью отошел от Mcrypt, и теперь шифрование основано на поддерживаемой библиотеке Libsodium.

Все ваши потребности в шифровании могут быть решены с помощью Libsodium библиотеки.

// On Alice's computer:
$msg = 'This comes from Alice.';
$signed_msg = sodium_crypto_sign($msg, $secret_sign_key);


// On Bob's computer:
$original_msg = sodium_crypto_sign_open($signed_msg, $alice_sign_publickey);
if ($original_msg === false) {
    throw new Exception('Invalid signature');
} else {
    echo $original_msg; // Displays "This comes from Alice."
}

Документация по Libsodium: https://github.com/paragonie/pecl-libsodium-doc

person Hemerson Varela    schedule 04.06.2018
comment
если вы вставляете какой-то код, убедитесь, что все переменные покрыты. В вашем примере $ secret_sign_key и $ alice_sign_publickey равны NULL - person undefinedman; 15.11.2018
comment
crypto_sign API не шифрует сообщения - для этого потребуется одна из crypto_aead_*_encrypt функций. - person Roger Dueck; 17.07.2019

ВАЖНО: этот ответ действителен только для PHP 5, в PHP 7 используются встроенные криптографические функции.

Вот простая, но достаточно безопасная реализация:

  • Шифрование AES-256 в режиме CBC
  • PBKDF2 для создания ключа шифрования из обычного текстового пароля
  • HMAC для аутентификации зашифрованного сообщения.

Код и примеры находятся здесь: https://stackoverflow.com/a/19445173/1387163

person Eugene Fidelin    schedule 23.06.2015
comment
Я не специалист по криптографии, но иметь ключ, полученный непосредственно из пароля, кажется ужасной идеей. Радужные таблицы + слабый пароль - и ваша безопасность исчезла. Также ваша ссылка указывает на функции mcrypt, которые устарели с PHP 7.1. - person Slava; 22.04.2018
comment
@ Alph.Dev вы правы, ответ выше действителен только для PHP 5 - person Eugene Fidelin; 10.01.2020