Найпростіше двостороннє шифрування за допомогою PHP


230

Який найпростіший спосіб зробити двостороннє шифрування у загальних установках PHP?

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

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



3
Для шифрування загального призначення використовуйте defuse / php-encryption / замість того, щоб прокручувати своє.
Скотт Арчішевський

2
Руки подалі від github.com/defuse/php-encryption - це на порядок повільніше, ніж mcrypt.
Євген Рік

1
@Scott Мислення за принципом "це, мабуть, не буде вузьким місцем" - це те, що принесло нам багато поганого програмного забезпечення.
Євген Рік

3
Якщо ви дійсно шифруєте / розшифровуєте багато даних до того, що це за мілісекунди це витрачає на ваш додаток, кусайте кулю і перейдіть на лібсодіум. Sodium::crypto_secretbox()і Sodium::crypto_secretbox_open()є надійними та надійними.
Скотт Арчішевський

Відповіді:


196

Відредаговано:

Ви дійсно повинні використовувати openssl_encrypt () & openssl_decrypt ()

Як каже Скотт , Макріпт не є хорошою ідеєю, оскільки він не оновлювався з 2007 року.

Існує навіть RFC для видалення Mcrypt з PHP - https://wiki.php.net/rfc/mcrypt-viking-funeral


6
@EugenRieck Так, у цьому справа. Mcrypt не отримує патчі. OpenSSL отримує виправлення, як тільки виявляється вразливість, велика чи мала.
Грег

5
було б краще для такої голосової відповіді, щоб там були найпростіші приклади у відповідь. все одно, дякую.
Т.Тодуа

хлопці, просто FYI => MCRYPT ВИДАЛЕНО. обмеження, тому кожен повинен знати, що не використовувати його, оскільки це дало нам безліч питань. Захищено з PHP 7.1, якщо я не помиляюся.
clusterBuddy

Оскільки PHP 7, функція mcrypt видаляється з кодової бази PHP. Отже, використовуючи останню версію php (яка повинна бути стандартною), ви більше не можете використовувати цю застарілу функцію.
Олександр Белінг

234

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

Бути проінформованим. Дизайн безпечних систем.

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

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

Якщо ваші цілі портативності не завадять вимагає розширення PECL, libsodium є високо рекомендується над чим ви або я можу написати в PHP.

Оновлення (2016-06-12): Тепер ви можете використовувати natry_compat і використовувати ті самі пропозиції криптовалюти, не встановлюючи розширення 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);

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

Вам буде набагато краще використовувати авторитетну бібліотеку криптовалют .


3
Отже, я просто намагаюся спочатку змусити UnsafeCrypto працювати. Шифрування буває добре, але щоразу, коли я запускаю розшифровку, я отримую "хибність" як відповідь. Я використовую той самий ключ для розшифровки та передачі істини на кодування, а також декодування. Існує те, що я припускаю, що в прикладі є тип, мені цікаво, чи звідки моя проблема. Чи можете ви пояснити, звідки береться змінна $ mac, і чи повинна вона бути просто $ iv?
Девід C

1
@EugenRieck Реалізація шифрів OpenSSL - це, мабуть, єдині частини, які не висмоктуються, і це єдиний спосіб використовувати AES-NI у ванільному PHP. Якщо ви встановите на OpenBSD, PHP буде скомпільовано проти LibreSSL без коду PHP, помітивши різницю. Libsodium> OpenSSL будь-який день. Також не використовуйте libmcrypt . Що б Ви порадили розробникам PHP використовувати замість OpenSSL?
Скотт Арчішевський



1
Я просто зробив це як демонстрацію того, що ви хочете, щоб ваші клавіші були бінарними рядками, а не людськими читатими рядками .
Скотт Арчішевський

22

Використовуйте mcrypt_encrypt()і mcrypt_decrypt()з відповідними параметрами. Дійсно легко і прямо вперед, і ви використовуєте протестований пакет шифрування.

EDIT

Через 5 років і 4 місяці після цієї відповіді mcryptрозширення наразі перебуває в стадії припинення та можливого відсторонення від PHP.


34
Битва перевірена і не оновлювалася більше 8 років?
Maarten Bodewes

2
Ну, mcrypt є в PHP7 і не застарілий - це досить добре для мене. Не весь код має жахливу якість OpenSSL і потребує виправлення кожні кілька днів.
Євген Рік

3
mcrypt не просто жахливий щодо підтримки. Він також не реалізовує кращих практик, таких як сумісність із PKCS № 7, автентифіковане шифрування. Він не підтримує SHA-3 або будь-який інший новий алгоритм, оскільки його ніхто не підтримує, позбавляючи вас шляху оновлення. Крім того, він прийняв такі речі, як часткові клавіші, виконання нульових прокладок і т.д.
Maarten Bodewes

2
У PHP 7.1 усі функції mcrypt_ * викликають повідомлення E_DEPRECATED. У PHP 7.1 + 1 (будь то 7.2 або 8.0) розширення mcrypt буде переміщене з ядра та в PECL, де люди, які дійсно хочуть його встановити, все ще можуть зробити це, якщо вони зможуть встановити розширення PHP від ​​PECL.
Младен Яньєтович

4

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
якщо вставити якийсь код, переконайтеся, що всі змінні охоплені. У вашому прикладі $ secret_sign_key та $ alice_sign_publickey - NULL
undefinedman

1
crypto_signAPI робить НЕ шифрувати повідомлення - це зажадає одна з crypto_aead_*_encryptфункцій.
Роджер

1

ВАЖЛИВО ця відповідь справедлива лише для PHP 5, у PHP 7 використовуйте вбудовані криптографічні функції.

Ось проста, але достатньо безпечна реалізація:

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

Код та приклади тут: https://stackoverflow.com/a/19445173/1387163


1
Я не експерт з криптографії, але мати ключ, отриманий безпосередньо з пароля, здається жахливою ідеєю. Таблиці веселки + слабкий пароль і пішов - це ваша безпека. Також ваше посилання вказує на функції mcrypt, які застаріли з PHP 7.1
Alph.Dev,

@ Alph.Dev ви правильні, відповідь вище є дійсною лише для PHP 5
Євген Фіделін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.