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

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

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

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

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

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

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

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

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

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

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

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

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

Обновление (2016-06-12): теперь вы можете использовать натрий_компат и использовать одни и те же предложения 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 поскольку он является отказоустойчивым и имеет неповрежденные ошибки, которые могут быть mcrypt безопасности. Из-за этих причин я рекомендую другим разработчикам PHP избежать этого.

Простой шифрование / расшифровка 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-encoded):

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

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

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

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

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

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

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

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