Прежде чем что-то делать дальше, постарайтесь понять разницу между шифрованием и аутентификацией , и почему вы, вероятно, хотите использовать аутентифицированное шифрование, а не просто шифрование .
Чтобы реализовать аутентифицированное шифрование, вы хотите зашифровать, а затем MAC. Порядок шифрования и аутентификации очень важен! Один из существующих ответов на этот вопрос сделал эту ошибку; как и многие криптографические библиотеки, написанные на PHP.
Вам следует избегать реализации собственной криптографии и использовать безопасную библиотеку, написанную и проверенную экспертами по криптографии.
Обновление: PHP 7.2 теперь предоставляет libsodium ! Для обеспечения максимальной безопасности обновите свои системы до версии PHP 7.2 или выше и следуйте только советам libsodium в этом ответе.
Используйте libsodium, если у вас есть доступ к PECL ( илиodium_compat, если вы хотите использовать libsodium без PECL); в противном случае ...
используйте defuse / php-encryption ; не катите свою собственную криптографию!
Обе библиотеки, указанные выше, позволяют легко и безболезненно внедрить аутентифицированное шифрование в ваши собственные библиотеки.
Если вы все еще хотите написать и развернуть свою собственную библиотеку криптографии, вопреки общепринятому мнению каждого криптографического эксперта в Интернете, вам следует предпринять следующие шаги.
Шифрование:
- Шифрование с использованием AES в режиме CTR. Вы также можете использовать GCM (что устраняет необходимость в отдельном MAC). Кроме того, ChaCha20 и Salsa20 (предоставляемые libsodium ) являются потоковыми шифрами и не требуют специальных режимов.
- Если вы не выбрали GCM выше, вы должны аутентифицировать зашифрованный текст с HMAC-SHA-256 (или, для потоковых шифров, Poly1305 - большинство API libsodium делают это за вас). MAC должен охватывать IV, а также зашифрованный текст!
Дешифрирование:
- Если не используется Poly1305 или GCM, пересчитайте MAC зашифрованного текста и сравните его с MAC, который был отправлен с использованием
hash_equals()
. Если это не удается, прервать.
- Расшифруйте сообщение.
Другие вопросы дизайна:
- Не сжимайте ничего никогда. Зашифрованный текст не сжимается; сжатие открытого текста перед шифрованием может привести к утечке информации (например, CRIME и BREACH в TLS).
- Убедитесь, что вы используете
mb_strlen()
и mb_substr()
, используя '8bit'
режим набора символов, чтобы предотвратить mbstring.func_overload
проблемы.
- IVs должны генерироваться с использованием CSPRNG ; Если вы используете
mcrypt_create_iv()
, НЕ ИСПОЛЬЗУЙТЕMCRYPT_RAND
!
- Если вы не используете конструкцию AEAD, ВСЕГДА шифруйте, а затем MAC!
bin2hex()
, base64_encode()
И т.д. может произойти утечка информации о ваших ключей шифрования с помощью синхронизации кэша. Избегайте их, если это возможно.
Даже если вы будете следовать советам, данным здесь, многое может пойти не так с криптографией. Всегда имейте криптографического эксперта, проверяющего вашу реализацию. Если вам не повезло дружить со студентом-криптографом в вашем местном университете, вы всегда можете обратиться за советом на форум по обмену стеками криптографии .
Если вам нужен профессиональный анализ вашей реализации, вы всегда можете нанять уважаемую команду консультантов по безопасности для проверки вашего криптографического кода PHP (раскрытие: мой работодатель).
Важно: когда не следует использовать шифрование
Не шифруйте пароли . Вместо этоговы хотите их хешировать , используя один из следующих алгоритмов хеширования паролей:
Никогда не используйте универсальную хеш-функцию (MD5, SHA256) для хранения пароля.
Не шифруйте параметры URL . Это неподходящий инструмент для работы.
Пример строкового шифрования PHP с помощью Libsodium
Если вы используете PHP <7.2 или у вас нет установленной библиотеки libsodium, вы можете использовать натрия_компат для достижения того же результата (хотя и медленнее).
<?php
declare(strict_types=1);
/**
* Encrypt a message
*
* @param string $message - message to encrypt
* @param string $key - encryption key
* @return string
* @throws RangeException
*/
function safeEncrypt(string $message, string $key): string
{
if (mb_strlen($key, '8bit') !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
throw new RangeException('Key is not the correct size (must be 32 bytes).');
}
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$cipher = base64_encode(
$nonce.
sodium_crypto_secretbox(
$message,
$nonce,
$key
)
);
sodium_memzero($message);
sodium_memzero($key);
return $cipher;
}
/**
* Decrypt a message
*
* @param string $encrypted - message encrypted with safeEncrypt()
* @param string $key - encryption key
* @return string
* @throws Exception
*/
function safeDecrypt(string $encrypted, string $key): string
{
$decoded = base64_decode($encrypted);
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
$plain = sodium_crypto_secretbox_open(
$ciphertext,
$nonce,
$key
);
if (!is_string($plain)) {
throw new Exception('Invalid MAC');
}
sodium_memzero($ciphertext);
sodium_memzero($key);
return $plain;
}
Затем, чтобы проверить это:
<?php
// This refers to the previous code block.
require "safeCrypto.php";
// Do this once then store it somehow:
$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
$message = 'We are all living in a yellow submarine';
$ciphertext = safeEncrypt($message, $key);
$plaintext = safeDecrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Halite - Libsodium Made Easy
Одним из проектов, над которым я работаю, является библиотека шифрования Halite , цель которой - сделать libsodium более простым и интуитивно понятным.
<?php
use \ParagonIE\Halite\KeyFactory;
use \ParagonIE\Halite\Symmetric\Crypto as SymmetricCrypto;
// Generate a new random symmetric-key encryption key. You're going to want to store this:
$key = new KeyFactory::generateEncryptionKey();
// To save your encryption key:
KeyFactory::save($key, '/path/to/secret.key');
// To load it again:
$loadedkey = KeyFactory::loadEncryptionKey('/path/to/secret.key');
$message = 'We are all living in a yellow submarine';
$ciphertext = SymmetricCrypto::encrypt($message, $key);
$plaintext = SymmetricCrypto::decrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Вся криптография лежит в основе libsodium.
Пример с defuse / php-шифрованием
<?php
/**
* This requires https://github.com/defuse/php-encryption
* php composer.phar require defuse/php-encryption
*/
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
require "vendor/autoload.php";
// Do this once then store it somehow:
$key = Key::createNewRandomKey();
$message = 'We are all living in a yellow submarine';
$ciphertext = Crypto::encrypt($message, $key);
$plaintext = Crypto::decrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Примечание : Crypto::encrypt()
возвращает шестнадцатеричный код.
Управление ключами шифрования
Если у вас возникнет желание использовать «пароль», остановитесь прямо сейчас. Вам нужен случайный 128-битный ключ шифрования, а не запоминающийся пароль.
Вы можете хранить ключ шифрования для долгосрочного использования следующим образом:
$storeMe = bin2hex($key);
И, по запросу, вы можете получить его так:
$key = hex2bin($storeMe);
Я настоятельно рекомендую просто хранить случайно сгенерированный ключ для долгосрочного использования вместо любого пароля в качестве ключа (или для получения ключа).
Если вы используете библиотеку Defuse:
«Но я действительно хочу использовать пароль».
Это плохая идея, но хорошо, вот как это сделать безопасно.
Сначала сгенерируйте случайный ключ и сохраните его в константе.
/**
* Replace this with your own salt!
* Use bin2hex() then add \x before every 2 hex characters, like so:
*/
define('MY_PBKDF2_SALT', "\x2d\xb7\x68\x1a\x28\x15\xbe\x06\x33\xa0\x7e\x0e\x8f\x79\xd5\xdf");
Обратите внимание, что вы добавляете дополнительную работу и можете просто использовать эту константу в качестве ключа и избавить себя от душевных страданий!
Затем используйте PBKDF2 (например, так), чтобы получить подходящий ключ шифрования из вашего пароля, а не шифровать его напрямую.
/**
* Get an AES key from a static password and a secret salt
*
* @param string $password Your weak password here
* @param int $keysize Number of bytes in encryption key
*/
function getKeyFromPassword($password, $keysize = 16)
{
return hash_pbkdf2(
'sha256',
$password,
MY_PBKDF2_SALT,
100000, // Number of iterations
$keysize,
true
);
}
Не просто используйте 16-значный пароль. Ваш ключ шифрования будет комично взломан.