Простейшее двустороннее шифрование с использованием PHP - PullRequest
202 голосов
/ 13 февраля 2012

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

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

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

Ответы [ 5 ]

203 голосов
/ 12 мая 2015

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

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

Переносимое шифрование данных в 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 также избегать этого.

Оболочка простого шифрования / дешифрования с использованием 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);

Demo : https://3v4l.org/jl7qR


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

Примечание : по умолчанию 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);

Демонстрации : raw двоичный , в кодировке base64


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

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

177 голосов
/ 13 февраля 2012

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

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

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

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

22 голосов
/ 13 февраля 2012

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

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

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

4 голосов
/ 04 июня 2018

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

2 голосов
/ 23 июня 2015

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

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

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

...