Увлекательная криптография или изыскания на тему обратимого шифрования на PHP. Руководство по шифрованию данных на PHP Хранение и управление ключами

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

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

Отчасти потому, что это интересно, отчасти потому, что моделируя что-то свое и сравнивая это с признанными стандартами, наглядно видишь контраст, эффективные решения и откровенные упущения, понимаешь, к чему можно стремиться для повышения эффективности.

Но довольно уже воды.

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

Итак, напишем собственную систему обратимого шифрования с приватным и публичным ключом, такую, которая будет обладать следующими признаками мало мальски защищенного криптографического алгоритма:

  1. Наличие шумовых символов в итоговом шифре.
  2. Информация в каждом канале Отправитель-Адресат будет шифроваться по приватному ключу, причем функция соответствия будет уникальной для каждого ключа.
  3. Каждое сообщение будет получать дайджест-код - некий уникальный код, являющийся функцией от приватного ключа и исходного сообщения. Это требуется для того, чтобы добиться уникальности функции соответствия «исходный символ <=> закодированный символ» не только для канала «Отправитель-Адресат», но и для каждого отдельного сообщения.

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

  4. Для усложнения частотного анализа будем кодировать каждый исходный символ сообщения двумя символами шифра.
Итак, что получилось.

Собственно говоря, итоговый результат посмотреть можно

Класс SymCoder включает в себя методы шифрования и дешифрования.

Шифрование осуществляет метод code(), принимающий на входе исходное сообщение.

Здесь сообщение по сгенерированной таблице соответствия в tab_coded создает зашифрованное сообщение, разбавленное по краям и внутри шумовыми символами.

Шумовые символы кстати уникальны для каждого канала отправитель-адресат, так как генерируются используя ключ канала, но не уникальны для сообщений. Используемые для шифрования символы в code_symbols представляют собой некоторые знаки пунктуации и символы вида %, @ и т.п.

На каждый кодируемый символ приходится два символа из code_symbols по понятным причинам, что их в несколько раз меньше, чем кодируемых символов.

Таблица соответствия create_tab_coded строится используя трансляцию хеша ключа сообщения в массив с количеством элементов, равным количеству элементов в массиве кодовых символов. Позиция начала обхода двухсимвольных кодов также всегда различна и связана с ключом канала. Это дает возможность быть уверенным, что алгоритм обхода кодируемых символов и соответствия им кодовых символов всегда (ну или гарантированно часто) будет различным.

К примеру, сообщение «привет, мир» будучи закодированным, выглядит вот так:

Digest-a00bf11d-&?==&!&?.@.@=!=-.?&1.#&?=:.:.1%!&-%@&@%~&1^#=?%%.!%+.?.~=?..&?%&&:%~.#%@&1&1.#=?.#.?.!&1==&=.-=!

А вот то же самое сообщение, закодированное снова:

Digest-a00bf11d-=:.?=:&!.?.1&-=:=?.?.=.?.!&=%!=-%@=!%~.=^#.1%%.!%+=:.~.@..==%&&1%~.1%@=?.@.!&=.!&@=:&1.==:=!.1&:

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

Сообщения обладают избыточностью, которая уменьшается по мере роста объема сообщения, в пределе доходя до 10% шума (для самых коротких сообщений шум достигает 90% и выше процентов), минимальная длина шифрованного сообщения - 116 символов. Один из некоторых минусов данного способа шифрования - увеличения кодированных сообщений как минимум в два раза.

Декодирование заключается в обратном переводе вида «кодовый символ» - исходный символ с вырезанием шума из сообщения. Что может быть в качестве ключа? В принципе, любая строка, уникальная для каждой пары вида адресат-получатель.

К примеру, если вы создаете мессенджер с шифрованием сообщений, в таком случае простейший вариант закрытого ключа может быть md5($user_id_1. $salt. $user_id_2), тогда ключ будет уникальный для каждого канала сообщений.

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

В качестве еще одного примера псевдошифрования приведу пример «шифрования» паролей в базе данных одной CMS – там пароли шифруются не в md5() или , а просто кодируются через base64. Т.е. при сливе базы хакеру не составит никого труда расшифровать все пароли через встроенную php-функцию base64_decode().

Нам же нужно передавать данные, не беспокоясь о том, что кто-то сможет перехватить текст и расшифровать его. В PHP есть популярный пакет шифрования данных Mcrypt, обеспечивающий возможность двустороннего шифрования (то есть собственно шифрование и расшифровку данных).

Mcrypt версии 2.4.7 поддерживает следующие алгоритмы симметричного шифрования: Blowfish, RC2, Safer-sk64 xtea, Cast-256, RC4, Safer-sk128, DES, RC4-iv, Serpent, Enigma, Rijndael-128, Threeway, Rijndael-192, TripleDES, LOKI97, Rijndael-256, Twofish, Panama, Saferplus и т.д. Подробнее про каждый алгоритм написано в Википедии.

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

Пример шифрования и расшифровки строки

mcrypt_module_open("des", "", "ecb", "")
Эта функция открывает модуль алгоритма и используемый режим. Для данного примера Алгоритм DES в режиме ECB.

$key = substr($key, 0, mcrypt_enc_get_key_size($td));
Максимальный размер ключа должен быть получен вызовом функции mcrypt_enc_get_key_size(), и каждое значение меньше полученного будет правильным.

$s = mcrypt_generic($td, $source);
При шифровании данные заполняются нулевыми байтами, чтобы гарантировать длину данных в n*blocksize. Размер блока blocksize определяется алгоритмом (для DES размер блока 64 бита). Поэтому при расшифровке в конце строки могут появиться “\0”, которые удаляются функцией trim()

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

Рассмотрим основные методы шифровки

base64 - позволяет шифровать и расшифровывать данные алгоритмом MIME base64. Он не использует ключей и его часто используют для скрытия ссылок в php.

Примеры:
//шифруем текст
$text = "Ссылка";
echo base64_encode($text); //Выдаст: PGEgaHJlZj0iIyI+0KHRgdGL0LvQutCwPC9hPg==
//дешифровка
echo base64_decode("PGEgaHJlZj0iIyI+0KHRgdGL0LvQutCwPC9hPg==");
?>

Как видно мы использовали сначала операцию base64_encode и получили шифр: PGEgaHJlZj0iIyI+0KHRgdGL0LvQutCwPC9hPg== , а затем подставили его в base64_decode и получили ссылку обратно.

md5 - позволяет хэшировать данные в одностороннем порядке. То есть в отличие от base64, вы уже не сможете их расшифровать обратно. Часто md5 используют для хранения паролей в БД, но в последнее время зашифрованную комбинацию md5 стало легко найти в таблицах по расшифровке, любезно предоставленную многими сайтами и алгоритмами. Поэтому для хранения паролей md5 лучше заменять на Blowfish алгоритмы.

Пример:

//шифруем текст
echo md5("комбинация");
?>

Шифрование по ключу

И последний пример шифровки/дешифровки, о котором я хотел рассказать использует ключ (как пароль). То есть в функцию шифрования вы передаете уникальный ключ, код шифруется вместе с ним. Для расшифровки вы должны предоставить функции зашифрованный код и ключ, который знаете только вы. Пример использования функций в самом низу кода.

function __encode($text, $key) {



$enc_text=base64_encode(mcrypt_generic ($td,$iv.$text));
mcrypt_generic_deinit ($td);
mcrypt_module_close ($td);
return $enc_text; } }
function strToHex($string) {
$hex="";
for ($i=0; $i < strlen($string); $i++) { $hex .= dechex(ord($string[$i])); }
return $hex; }
function __decode($text, $key) {
$td = mcrypt_module_open ("tripledes", "", "cfb", "");
$iv_size = mcrypt_enc_get_iv_size ($td);
$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size ($td), MCRYPT_RAND);
if (mcrypt_generic_init ($td, $key, $iv) != -1) {
$decode_text = substr(mdecrypt_generic ($td, base64_decode($text)),$iv_size);
mcrypt_generic_deinit ($td);
mcrypt_module_close ($td);
return $decode_text; } }
function hexToStr($hex) {
$string="";
for ($i=0; $i < strlen($hex)-1; $i+=2) { $string .= chr(hexdec($hex[$i].$hex[$i+1])); }
return $string; }

$str = "Булочки, которые надо зашифровать!
По ключу";
$code = strToHex(__encode($str, "My#key-do-36-simvolov"));
echo "Зашифрованный код: ".$code."
";

$str = __decode(hexToStr($code), "My#key-do-36-simvolov");
echo "Расшифрованный код: ".$str."
";
?>

Шифровать можно с html содержимым. Длина ключа должна быть не более 36 символов.

Этот метод можно использовать для шифровки каких-то данных и их помещения в txt файл или БД, а получать с помощью дешифровки с ключем.

Конечно, любой код можно расшифровать/взломать и этот не исключение, поэтому используйте надежные методы шифрования.

(PHP 4, PHP 5, PHP 7)

crypt — One-way string hashing

Warning

This function is not (yet) binary safe!

Description

crypt (string $str [, string $salt ]) : string

crypt() will return a hashed string using the standard Unix DES -based algorithm or alternative algorithms that may be available on the system.

The salt parameter is optional. However, crypt() creates a weak hash without the salt . PHP 5.6 or later raise an E_NOTICE error without it. Make sure to specify a strong enough salt for better security.

password_hash() uses a strong hash, generates a strong salt, and applies proper rounds automatically. password_hash() is a simple crypt() wrapper and compatible with existing password hashes. Use of password_hash() is encouraged.

Some operating systems support more than one type of hash. In fact, sometimes the standard DES-based algorithm is replaced by an MD5-based algorithm. The hash type is triggered by the salt argument. Prior to 5.3, PHP would determine the available algorithms at install-time based on the system"s crypt(). If no salt is provided, PHP will auto-generate either a standard two character (DES) salt, or a twelve character (MD5), depending on the availability of MD5 crypt(). PHP sets a constant named CRYPT_SALT_LENGTH which indicates the longest valid salt allowed by the available hashes.

The standard DES-based crypt() returns the salt as the first two characters of the output. It also only uses the first eight characters of str , so longer strings that start with the same eight characters will generate the same result (when the same salt is used).

On systems where the crypt() function supports multiple hash types, the following constants are set to 0 or 1 depending on whether the given type is available:

  • CRYPT_STD_DES - Standard DES-based hash with a two character salt from the alphabet "./0-9A-Za-z". Using invalid characters in the salt will cause crypt() to fail.
  • CRYPT_EXT_DES - Extended DES-based hash. The "salt" is a 9-character string consisting of an underscore followed by 4 bytes of iteration count and 4 bytes of salt. These are encoded as printable characters, 6 bits per character, least significant character first. The values 0 to 63 are encoded as "./0-9A-Za-z". Using invalid characters in the salt will cause crypt() to fail.
  • CRYPT_MD5 - MD5 hashing with a twelve character salt starting with $1$
  • CRYPT_BLOWFISH - Blowfish hashing with a salt as follows: "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters from the alphabet "./0-9A-Za-z". Using characters outside of this range in the salt will cause crypt() to return a zero-length string. The two digit cost parameter is the base-2 logarithm of the iteration count for the underlying Blowfish-based hashing algorithmeter and must be in range 04-31, values outside this range will cause crypt() to fail. Versions of PHP before 5.3.7 only support "$2a$" as the salt prefix: PHP 5.3.7 introduced the new prefixes to fix a security weakness in the Blowfish implementation. Please refer to for full details of the security fix, but to summarise, developers targeting only PHP 5.3.7 and later should use "$2y$" in preference to "$2a$".
  • CRYPT_SHA256 - SHA-256 hash with a sixteen character salt prefixed with $5$. If the salt string starts with "rounds=
  • CRYPT_SHA512 - SHA-512 hash with a sixteen character salt prefixed with $6$. If the salt string starts with "rounds=$", the numeric value of N is used to indicate how many times the hashing loop should be executed, much like the cost parameter on Blowfish. The default number of rounds is 5000, there is a minimum of 1000 and a maximum of 999,999,999. Any selection of N outside this range will be truncated to the nearest limit.

As of PHP 5.3.0, PHP contains its own implementation and will use that if the system lacks of support for one or more of the algorithms.

Parameters

The string to be hashed.

Caution

Using the CRYPT_BLOWFISH algorithm, will result in the str parameter being truncated to a maximum length of 72 characters.

An optional salt string to base the hashing on. If not provided, the behaviour is defined by the algorithm implementation and can lead to unexpected results.

Return Values

Returns the hashed string or a string that is shorter than 13 characters and is guaranteed to differ from the salt on failure.

Warning

When validating passwords, a string comparison function that isn"t vulnerable to timing attacks should be used to compare the output of crypt() to the previously known hash. PHP 5.6 onwards provides hash_equals() for this purpose.

Changelog

Version Description
5.6.5 When the failure string "*0" is given as the salt , "*1" will now be returned for consistency with other crypt implementations. Prior to this version, PHP 5.6 would incorrectly return a DES hash.
5.6.0 Raise E_NOTICE security warning if salt is omitted.
5.5.21 When the failure string "*0" is given as the salt , "*1" will now be returned for consistency with other crypt implementations. Prior to this version, PHP 5.5 (and earlier branches) would incorrectly return a DES hash.
5.3.7 Added $2x$ and $2y$ Blowfish modes to deal with potential high-bit attacks.
5.3.2 Added SHA-256 and SHA-512 crypt based on Ulrich Drepper"s » implementation .
5.3.2 Fixed Blowfish behaviour on invalid rounds to return "failure" string ("*0" or "*1"), instead of falling back to DES.
5.3.0 PHP now contains its own implementation for the MD5 crypt, Standard DES, Extended DES and the Blowfish algorithms and will use that if the system lacks of support for one or more of the algorithms.

Examples

Example #1 crypt() examples

$hashed_password = crypt ("mypassword" ); // let the salt be automatically generated

/* You should pass the entire results of crypt() as the salt for comparing a
password, to avoid problems when different hashing algorithms are used. (As
it says above, standard DES-based password hashing uses a 2-character salt,
but MD5-based hashing uses 12.) */
if (hash_equals ($hashed_password , crypt ($user_input , $hashed_password ))) {
echo "Password verified!" ;
}
?>

Example #2 Using crypt() with htpasswd

// Set the password
$password = "mypassword" ;

// Get the hash, letting the salt be automatically generated
$hash = crypt ($password );
?>

Example #3 Using crypt() with different hash types

/* These salts are examples only, and should not be used verbatim in your code.
You should generate a distinct, correctly-formatted salt for each password.
*/
if (CRYPT_STD_DES == 1 ) {
echo "Standard DES: " . crypt ("rasmuslerdorf" , "rl" ) . "\n" ;
}

if (CRYPT_EXT_DES == 1 ) {
echo "Extended DES: " . crypt ("rasmuslerdorf" , "_J9..rasm" ) . "\n" ;
}

if (CRYPT_MD5 == 1 ) {
echo "MD5: " . crypt ("rasmuslerdorf" , "$1$rasmusle$" ) . "\n" ;
}

if (CRYPT_BLOWFISH == 1 ) {
echo "Blowfish: " . crypt ("rasmuslerdorf" , "$2a$07$usesomesillystringforsalt$" ) . "\n" ;
}

if (CRYPT_SHA256 == 1 ) {
echo "SHA-256: " . crypt ("rasmuslerdorf" , "$5$rounds=5000$usesomesillystringforsalt$" ) . "\n" ;
}

if (CRYPT_SHA512 == 1 ) {
echo "SHA-512: " . crypt ("rasmuslerdorf" , "$6$rounds=5000$usesomesillystringforsalt$" ) . "\n" ;
}
?>