it-roy-ru.com

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

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

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

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

199
user1206970

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

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

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

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

177
472084

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

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

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

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

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

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

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


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

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

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

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

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

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

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

Simple Encryption/Decryption Wrapper с использованием 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/f5K9

Оболочка простой аутентификации

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

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

199
Scott Arciszewski

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

РЕДАКТИРОВАТЬ

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

22
Eugen Rieck

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

3
Hemerson Varela

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

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

Код и примеры здесь: https://stackoverflow.com/a/19445173/138716

2
Eugene Fidelin