Какой самый простой способ сделать двустороннее шифрование в обычных установках PHP?
Мне нужно иметь возможность шифровать данные с помощью строкового ключа и использовать тот же ключ для расшифровки на другом конце.
Безопасность не так важна, как переносимость кода, поэтому я бы хотел, чтобы все было как можно проще. В настоящее время я использую реализацию RC4, но если я смогу найти что-то изначально поддерживаемое, я думаю, что я могу сохранить много ненужного кода.
Отредактировано:
Вы действительно должны использовать openssl_encrypt () & openssl_decrypt ()
Как Скотт говорит, что Mcrypt не очень хорошая идея, так как он не обновлялся с 2007 года.
Существует даже RFC для удаления Mcrypt из PHP — https://wiki.php.net/rfc/mcrypt-viking-funeral
Важный: Если у вас нет очень конкретный вариант использования, не шифруйте пароли, вместо этого используйте алгоритм хеширования пароля. Когда кто-то говорит, что они шифровать их пароли в серверном приложении, они либо не информированы, либо описывают опасный дизайн системы. Безопасное хранение паролей это совершенно отдельная проблема от шифрования.
Быть информированным. Проектирование безопасных систем.
Если вы используете PHP 5.4 или новее и не хочу писать криптографический модуль самостоятельно, я рекомендую использовать существующая библиотека, которая обеспечивает аутентифицированное шифрование. Библиотека, на которую я ссылаюсь, опирается только на то, что предоставляет PHP, и периодически проверяется горсткой исследователей безопасности. (Я в том числе.)
Если ваши цели переносимости не мешают требовать расширения PECL, libsodium является высоко рекомендуется по всему, что вы или я могу написать на PHP.
Обновление (2016-06-12): Теперь вы можете использовать sodium_compat и использовать те же предложения crypto libsodium без установки расширений PECL.
Если вы хотите попробовать себя в криптографии, читайте дальше.
Во-первых, вы должны потратить время на изучение опасности неаутентифицированного шифрования а также криптографический принцип Doom.
Шифрование в PHP на самом деле просто (мы будем использовать openssl_encrypt()
а также openssl_decrypt()
как только вы приняли решение о том, как зашифровать вашу информацию. советоваться openssl_get_cipher_methods()
для получения списка методов, поддерживаемых в вашей системе. Лучший выбор AES в режиме CTR:
aes-128-ctr
aes-192-ctr
aes-256-ctr
В настоящее время нет оснований полагать, что Размер ключа AES является серьезной проблемой, о которой стоит беспокоиться не лучше из-за плохого планирования ключей в 256-битном режиме).
Замечания: Мы не используем mcrypt
потому что это так Abandonware и имеет исправленные ошибки это может повлиять на безопасность. По этим причинам я призываю других разработчиков PHP также избегать этого.
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
библиотека в производственной среде, или ваша собственная реализация тех же концепций, я настоятельно рекомендую обратиться к ваши резидентные криптографы для второго мнения, прежде чем сделать. Они смогут рассказать вам об ошибках, о которых я даже не подозреваю.
Вам будет гораздо лучше использовать авторитетная криптографическая библиотека.
использование mcrypt_encrypt()
а также mcrypt_decrypt()
с соответствующими параметрами. Действительно легко и просто, и вы используете проверенный в бою пакет шифрования.
РЕДАКТИРОВАТЬ
5 лет и 4 месяца после этого ответа mcrypt
расширение в настоящее время находится в процессе устаревания и возможного удаления из PHP.
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
Вот простая, но достаточно безопасная реализация:
Код и примеры здесь: https://stackoverflow.com/a/19445173/1387163