Публикации
Публикации  »  PHP

Обратимое шифрование по ключу на PHP

Задача надежного шифрования текстовой информации часто встречается при программировании сайтов. В зашифрованном виде бывает необходимо хранить не только пароли, но и другую информацию. Недавно такая задача встала и у меня. Мне нужна была более-менее надежная функция обратимого шифрования текста по ключу. Почему по ключу? Дело в том, что шифрация без ключа может быть взломана, т.к. большинство алгоритмов шифрования можно найти в интернете и подобрать способ, чтобы получить исходные данные, а шифрация с ключом гораздо более надежная.

Поискав по интернету я нашел целых 2 достаточно коротких в плане количества кода и в тоже время очень надежных способа обратимого шифрования по ключу, которые использует встроенную в php библиотеку Mcrypt.

На подавляющем большинстве хостингов данная библиотека сразу же идет вместе с php. Но если вы администрируете свой сервер и данной библиотеки почему-то вдруг не оказалось в составе php, вы всегда можете ее доустановить командой apt-get install php5-mcrypt для Debian-подобных систем (в т.ч. Mint, Ubuntu) или yum install php-mcrypt для RedHat-подобных систем (в т.ч. Fedora, openSUSE, CentOS) или любым другим способом, который вам нравится (через dpkg, rpm, yast и т.д.).

Затем в папке /etc находите папку php, содержащую ini-файлы расширений, загружаемых php по-умолчанию. Посмотреть путь до этой папки можно в php.ini в разделе "Dynamic Extensions". Это может быть папка /etc/php или /etc/php5/mods-available/ или как у меня на сервере /etc/php.d (вообщем, зависит от настроек php). В этой папке должен присутствовать файл mcrypt.ini. Если его там нет, тогда создайте его с таким содержимым:

; Enable mcrypt extension module
extension=mcrypt.so

После этого можно включить расширение командой php5enmod mcrypt, а затем перезапустить сервер /etc/init.d/apache2 restart для Debian-систем или service httpd restart для RedHat систем. Разумеется, все описанные действия выполняются с правами root-а.

Итак, теперь приведу примеры 2-х способов шифрования по ключу, которые я нашел для себя в интернете.

1-й способ. Обратимое шифрование по произвольному ключу.

define('ENCRYPTION_KEY', '_91X:s+{a2Jwb6*J');

$txt = 'Тестируем обратимое шифрование на php';
$encrypted = encrypt($txt, ENCRYPTION_KEY);
echo $encrypted.'<br>';
$decrypted = decrypt($encrypted, ENCRYPTION_KEY);
echo $decrypted;

function encrypt($decrypted, $key) {
  $ekey = hash('SHA256', $key, true);
  srand(); $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
  if (strlen($iv_base64 = rtrim(base64_encode($iv), '=')) != 22) return false;
  $encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $ekey, $decrypted . md5($decrypted), MCRYPT_MODE_CBC, $iv));
  return $iv_base64 . $encrypted;
}

function decrypt($encrypted, $key) {
  $ekey = hash('SHA256', $key, true);
  $iv = base64_decode(substr($encrypted, 0, 22) . '==');
  $encrypted = substr($encrypted, 22);
  $decrypted = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $ekey, base64_decode($encrypted), MCRYPT_MODE_CBC, $iv), "\0\4");
  $hash = substr($decrypted, -32);
  $decrypted = substr($decrypted, 0, -32);
  if (md5($decrypted) != $hash) return false;
  return $decrypted;
}

2-й способ. Очень надежное обратимое шифрование по шестандцатиричному ключу.

Внимание! Ключ должен быть шестандцатиричным (символы 0123456789ABCDEF) длиной 32 или 64 символа.

define('ENCRYPTION_KEY', 'e3f080b6edfcf6fff70654021c7c2e43');

$txt = 'Тестируем обратимое шифрование на php';
$encrypted = mc_encrypt($txt, ENCRYPTION_KEY);
echo $encrypted.'<br>';
$decrypted = mc_decrypt($encrypted, ENCRYPTION_KEY);
echo $decrypted;

// Encrypt Function
function mc_encrypt($encrypt, $key) {
  $encrypt = serialize($encrypt);
  $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC), MCRYPT_DEV_URANDOM);
  $key = pack('H*', $key);
  $mac = hash_hmac('sha256', $encrypt, substr(bin2hex($key), -32));
  $passcrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $encrypt.$mac, MCRYPT_MODE_CBC, $iv);
  $encoded = base64_encode($passcrypt).'|'.base64_encode($iv);
  return $encoded;
}

// Decrypt Function
function mc_decrypt($decrypt, $key) {
  $decrypt = explode('|', $decrypt.'|');
  $decoded = base64_decode($decrypt[0]);
  $iv = base64_decode($decrypt[1]);
  if(strlen($iv)!==mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC)){ return false; }
  $key = pack('H*', $key);
  $decrypted = trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $decoded, MCRYPT_MODE_CBC, $iv));
  $mac = substr($decrypted, -64);
  $decrypted = substr($decrypted, 0, -64);
  $calcmac = hash_hmac('sha256', $decrypted, substr(bin2hex($key), -32));
  if($calcmac!==$mac){ return false; }
  $decrypted = unserialize($decrypted);
  return $decrypted;
}

Примечательно, что если выполнить приведенные мной примеры несколько раз, мы увидим каждый раз разные зашифрованные данные, хотя шифровался один и тот же текст. При этом, хоть и зашифрованные данные выглядят по-разному, результат расшифровки всегда один и тот же - исходный текст. Подробнее почитать про библиотеку шифрования можно на php.net. На ее основе можно придумать и свой, уникальный способ обратимого шифрования.

Обратимое шифрование на PHP 7 библиотекой OpenSSL

Функции библиотеки Mcrypt, такие как mcrypt_encrypt и mcrypt_decrypt считаются устаревшими и не рекомендуют их использовать, т.к. когда-нибудь в будущих версиях php они перестанут существовать. Вместо них предлагается использовать openssl_encrypt и openssl_decrypt из библиотеки OpenSSL. Эта библиотека содержит множество различных методов и алгоритмов симметричного и асимметричного шифрования. Пока что приведу один пример шифрования этими функциями, взятый с php.net а в будущем, доработаю статью, добавив еще примеры.

define('ENCRYPTION_KEY', 'ab86d144e3f080b61c7c2e43');

// Encrypt
$plaintext = "Тестируем обратимое шифрование на php 7";
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, ENCRYPTION_KEY, $options=OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, ENCRYPTION_KEY, $as_binary=true);
$ciphertext = base64_encode( $iv.$hmac.$ciphertext_raw );
echo $ciphertext.'<br>';

// Decrypt
$c = base64_decode($ciphertext);
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = substr($c, 0, $ivlen);
$hmac = substr($c, $ivlen, $sha2len=32);
$ciphertext_raw = substr($c, $ivlen+$sha2len);
$plaintext = openssl_decrypt($ciphertext_raw, $cipher, ENCRYPTION_KEY, $options=OPENSSL_RAW_DATA, $iv);
$calcmac = hash_hmac('sha256', $ciphertext_raw, ENCRYPTION_KEY, $as_binary=true);
if (hash_equals($hmac, $calcmac))
{
    echo $plaintext;
}

Обратите внимание: этот алгоритм будет работать начиная с PHP 5.6 и выше. На предыдущих версиях будет выдавать ошибку из-за функции hash_equals, которая осуществляет сравнение строк нечувствительное к атакам по времени (подробнее про атаки по времени можете почитать на википедии).

Другими альтернативами для шифрования на PHP 7+ являются библиотеки: Libsodium и defuse/php-encryption.

 

Категория: PHP

Комментарии к статье:

03.10.17   Павел Спасибо, полезный алгоритм)
02.11.17   Гость Спасибо. Еще вопросик. Как возможно наиболее коротко упаковать значение текущей даты-времени (в формате YmdHisu, т.е. год,месяц,..миллисекунды) в короткую символьную строку (цифры и буквы)? В base64 увы, длинновато. И если это возможно, стандартными средствами php 5.4, без доп.расширений вроде GMP и т.п.
03.11.17   Гость GMP функции разрешены на 99% хостингах. Но если все-таки пользоваться только стандартными функциями и больше ничем - значит, придумывать какой-то свой алгоритм или поискать и реализовать готовые алгоритмы, например, алгоритм Хаффмана.
21.05.18   Андрей Например, 21 May 2018 г. 08:45:47 543мс:
Это timestamp: 1526892347543.
А переведя в 36-ричную систему (0...9A...Z) получим JHG0A3KN (8символов!)
Этого достаточно?
Пишите - phonetoyou [гав] gmail [дот] com.
21.05.18   Андрей можно сократить до 6 (шести) !! если использовать 128ричную систему "0123456789aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZаАбБвВгГдДеЕёЁжЖзЗиИйЙкКлЛмМнНоОпПрРсСтТуУфФхХцЦчЧшШщЩъЪыЫьЬэЭюЮяЯ".
Вот функции:
function NUMtoSTRING($NUMBER, $ALPHA = '0123456789aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZаАбБвВгГдДеЕёЁжЖзЗиИйЙкКлЛмМнНоОпПрРсСтТуУфФхХцЦчЧшШщЩъЪыЫьЬэЭюЮяЯ'){
	$ALPHA_LEN = mb_strlen($ALPHA, 'UTF-8');
	$STRNG = '';
	while($NUMBER > $ALPHA_LEN){
		$STRNG = mb_substr($ALPHA, $NUMBER % $ALPHA_LEN, 1, 'UTF-8') . $STRNG;
		$NUMBER = floor($NUMBER / $ALPHA_LEN);
	}
	$STRNG = mb_substr($ALPHA, $NUMBER, 1, 'UTF-8') . $STRNG;
	return $STRNG;
}
echo NUMtoSTRING(1526892347543); // "rxC11G" - 6 символов

function STRINGtoNUM($STRNG, $ALPHA = '0123456789aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZаАбБвВгГдДеЕёЁжЖзЗиИйЙкКлЛмМнНоОпПрРсСтТуУфФхХцЦчЧшШщЩъЪыЫьЬэЭюЮяЯ'){
	$ALPHA_LEN = mb_strlen($ALPHA, 'UTF-8');
	$STRNG_LEN = mb_strlen($STRNG, 'UTF-8');
	$NUMBER = 0;
	for($S_i = 0; $S_i < $STRNG_LEN; $S_i++)
		$NUMBER += mb_strpos($ALPHA, mb_substr($STRNG, $S_i, 1, 'UTF-8'), 0, 'UTF-8') * pow($ALPHA_LEN, $STRNG_LEN - $S_i - 1);
	return $NUMBER;
}
echo STRINGtoNUM('rxC11G'); // вернет нам наши 1526892347543
24.05.18   Гость Пример хороший... Особенно понравилась глобальная переменная...
01.09.18   Гость круто
23.11.18   Гость Могу ли я положить место параметра $_POST запрос ?
07.12.18   Гость Гость, в функцию $_POST, $_GET, $_REQUEST и другие глобальные переменные никто пишет. Передавай аргументов в функцию, а лучше почитай документацию по рнр, если не знаешь как написать функцию.
08.12.18   Гость "Пример хороший... Особенно понравилась глобальная переменная..." школьник, ты придурок. Во-первых тут нет глобальных переменных, во-вторых ты не сможешь объяснить чем плохи глобальные переменные, ну и в третьих ты будешь лохом по жизни, так как пытаешься самоутверждаться как лох.

Добавить комментарий: