Привет Хабр!
У меня время от времени возникает необходимость зашифровать какие-то данные в своих проектах, но неохота использовать готовые решения.
Поэтому я изобрел свой велосипед. Это почти простой XOR.

Проблема ХОR-шифрования.


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

У меня — что-то вроде генератора псевдослучайных чисел.
И так, процедура шифрования:

1. Оригинальная строка конвертируется в HEX, затем разбивается на части по 2 символа
$encoded = bin2hex($str);
$encoded = str_split($encoded, 2);

2. Генерикуем ключ
$newkey = md5($key);
for($i=0;$i<1000;$i++){
	$newkey = $newkey.substr(md5($newkey.$i), 6, 1);
}
$key = $newkey;//Генерируем новый пароль

$binkey = '';
for($i=0;$i<(count($encoded)/16)+1;$i++){//Хэшируем пароль с мусором пока не наберем нужную длину
	$binkey .= md5($key.$binkey.$secret.md5($i));
}
$binkey = str_split($binkey, 2);//тоже разбиваетм на части по 2 символа

3. Ксорим
for ($i=0;$i<count($encoded);$i++){
	$a = $encoded[$i];
	$returnment .= bin2hex(
		pack('H*', $a)
		^
		pack('H*', $binkey[$i])
	);
}

4. Конвертируем назад в бинарные данные, потом в base64. Заменяем спецсимволы base64
$returnment = hex2bin($returnment);
$returnment = base64_encode($returnment);
$returnment = str_replace('+', '-', $returnment);
$returnment = str_replace('/', '_', $returnment);


Вот что у нас получилось:
$secret = md5("VIKA").md5("ONE").md5("LOVE").md5("md5('!')").md5("SECRET_PASSWAD");

$encoded = bin2hex($str);
$encoded = str_split($encoded, 2);

$newkey = md5($key);
for($i=0;$i<1000;$i++){
	$newkey = $newkey.substr(md5($newkey.$i), 6, 1);
}
$key = $newkey;

$binkey = '';
for($i=0;$i<(count($encoded)/16)+1;$i++){
	$binkey .= md5($key.$binkey.$secret.md5($i));
}
$binkey = str_split($binkey, 2);

$returnment = '';
for ($i=0;$i<count($encoded);$i++){
	$a = $encoded[$i];
	$returnment .= bin2hex(
		pack('H*', $a)
		^
		pack('H*', $binkey[$i])
	);
}
$returnment = hex2bin($returnment);
$returnment = base64_encode($returnment);
$returnment = str_replace('+', '-', $returnment);
$returnment = str_replace('/', '_', $returnment);
return $returnment;


Расшифровываем так же.
$secret = md5("VIKA").md5("ONE").md5("LOVE").md5("md5('!')").md5("SECRET_PASSWAD");

$str = str_replace('-', '+', $str);
$str = str_replace('_', '/', $str);
$str = base64_decode($str);
$str = bin2hex($str);
$str = str_split($str, 2);

$newkey = md5($key);
for($i=0;$i<1000;$i++){
	$newkey = $newkey.substr(md5($newkey.$i), 6, 1);
}
$key = $newkey;

$binkey = '';
for($i=0;$i<(count($str)/16)+1;$i++){
	$binkey .= md5($key.$binkey.$secret.md5($i));
}
$binkey = str_split($binkey, 2);

$returnment = '';
for ($i=0;$i<count($str);$i++){
	$a = $str[$i];
	$returnment .= (
		pack('H*', $a)
		^
		pack('H*', $binkey[$i])
	);
}
return $returnment;

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

Ну, вот и все.
Пример зашифрованной строки:
0uo0XM5LX_OR_iwssLfV4NHPVdrV0l7vqXobjYOFqltJRdqPHz3hDnmPm8ibFRgEE_xgrLT4oN4=

Код на GitHub: https://github.com/da411d/Crypto228
Демка: http://app.blastorq.pp.ua/crypto228/

UPD: Обновил код, теперь на пару строк больше говнокода пароль тысячу раз хешуеться, а «ключевой поток» теперь немножко сложнее. Также есть дополнительный мастер-пароль.

Комментарии (25)


  1. nmk2002
    15.03.2016 23:39
    +25

    Я думаю, что я немного понимаю в криптографии, поэтому позволю себе прокомментировать.
    В моем понимании, то, что вы описали — кошмар. Вы используете ключ из которого, не добавляя энтропию, генерируете ключ большей длины. По сути криптостойкость полученного длинного ключа не отличается от исходного ключа.
    Шифрование, которое вы хотели получить — one time pad. Но для one time pad нужен исходный ключ равный по длине тексту, который вы шифруете.
    Рекомендую пройти курс https://www.coursera.org/learn/crypto. Очень полезно для понимания того, что в криптографию лучше не лезть, пока не понимаешь ее на хорошем уровне. Легко наделать ошибки в неожиданных местах.


    1. nazarpc
      15.03.2016 23:42
      +2

      Упомянутый курс отличный, тоже рекомендую


  1. nazarpc
    15.03.2016 23:39
    +17

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

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

    Смешно


    1. Fen1kz
      15.03.2016 23:45
      -15

      1) Чувак не изобретал свой шифр, а взял XOR
      2) Ага, лучше вообще ничего не делать и ничему не учиться, слепо доверять специалистам, они умные и хорошие.

      Не в защиту автора, но больно вы зазнались.


      1. nazarpc
        16.03.2016 00:00
        +10

        1) Тогда у него был бы цикл где посимвольно применяется xor, но я считаю что у него как и во многих других шифрах xor всего лишь один из базовых компонентов алгоритма
        2) Вот тут вы как раз правы — нужно учиться вместо того чтобы считать что они плохие и глупые, а я вот такой умный сейчас напишу всё как надо; лучше посмотреть с чего люди начинали, куда двигались, какие были популярные (и есть) алгоритмы, какие в них есть уязвимости, как и когда их можно эксплуатировать, попробовать взломать шифр с известной уязвимостью — вот это всё будет гораздо полезнее дырявого алгоритма, который ещё может кто-то в последствии найти и добавить в реальный проект

        Как обычно советуют в таких криптографических постах — лучше упрятать в черновики чтобы никто не видел и забыть.
        Я понимаю что парень старался, ничего личного, но эту реализацию лучше удалить безвозвратно.


      1. lair
        16.03.2016 00:38
        +5

        Чувак не изобретал свой шифр, а взял XOR

        … не понимая его уязвимостей.

        Ага, лучше вообще ничего не делать и ничему не учиться, слепо доверять специалистам, они умные и хорошие.

        Лучше как раз учиться.


  1. lair
    16.03.2016 00:37
    +6

    Как вы думаете, что случится, если атакующий получит в свое распоряжение два криптотекста, зашифрованных вашим шифром с одним и тем же паролем?


    1. Arrest
      16.03.2016 01:51
      -1

      Очевидный ответ — нужно сортировать отправляемые сообщения по длине в обратном порядке, а при достижении минимума менять ключ =)


      1. Arrest
        16.03.2016 01:54
        +2

        Ой, он же там конкатенирует ключ предыдущего раунда и H(round_{n-1}, k). Мда.


      1. lair
        16.03.2016 01:54
        +2

        И что изменится?


  1. MichaelBorisov
    16.03.2016 01:22
    +4

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

    Почему столько людей по-прежнему пытаются изобрести велосипед?

    Ведь это уже перестало быть "проще", чем реализовать какой-нибудь из стандартных.

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

    К тому же, предлагаемый "шифр" полагается на md5 как на базовый примитив. Тем более удивительно. Почему автор доверяет md5 из библиотечной реализации, но не доверяет, к примеру, AES?

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


  1. barsulka
    16.03.2016 06:29
    +7

    Давид, ну вы опять за своё...

    Безусловно, это хорошо, что старшеклассник (1999 г.р. — или первокурсник?) интересуется программированием. Уверен, подавляющее большинство так же начинало с малого — я, к примеру, лет 15 назад изменял готовые скрипты часов на JS, и любой успешный шаг в этом нелёгком деле был сродни оргазму.

    Тем не менее, мне кажется, что вам не следует каждым своим Великим Открытием вроде "как сгенерировать картинку на PHP" или "как заменить подстроку на PHP" делиться с аудиторией Хабра. Ваши посты (включая этот) минусуют совсем не из-за того, что вокруг собрались злые упыри.

    Поберегите карму и свой аккаунт до лучших времён. Успехов вам в начинаниях.

    Постскриптум по теме: такое "шифрование" нельзя использовать нигде и никогда.


  1. iUser
    16.03.2016 06:48
    +1

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

    Что-бы хоть как-то улучшить велосипед, нужно, как минимум, исправить

    $binkey .= md5($key.$binkey);

    хотя бы на

    $binkey .= md5($binkey.$i.$key.$binkey);

    Ну и поскольку все в конечном итоге зависит только от $key, то и его перед первым употреблением надо через хоть какую-нибудь kdf прогнать, типа

    $tmp = $key;
    for($i=0; $i< многораз; $i++) { $tmp = md5($tmp.$i.$key); }
    $key = $tmp;

    Это необходимый минимум изменений, которого все равно не достаточно.


    1. da411d
      16.03.2016 20:12

      Так и сделал


  1. vilgeforce
    16.03.2016 10:04
    +2

    Главная "Проблема ХОР-шифрования" состоит в том, что оно все же XOR.


  1. StrangerInRed
    16.03.2016 12:55

    Можно использовать hash-based xor, если использовать xor-хеширование по одноразовому ключу (s-key только для других хешфункций), но все равно остается проблема что скорость брутфорса очень велика.
    Для этого в шифры добавляют раунды, перестановки и подстановки.

    Для любителей велосипедов: https://habrahabr.ru/post/260321/


  1. Ivan_83
    16.03.2016 16:34

    Это обычный поточный шифр.

    Сделать генератор ключевого потока ($binkey) на базе хэш функции — оригинально, но:
    0. Преобразование в HEX — ненужная фигня, как и замена потом. Шифруем то что есть, а если нужно читаемое — делаем это отдельно/потом.
    1. Первый блок ксорится хэшэм от ключа, если знать открытый текст то можно попробовать в радужных таблицах найти пароль.
    2. Крайне низкая производительность (крипто хэши относительно медленные функции сами по себе), драматически падающая при увеличении размера открытого текста.
    3. Не возможность быстро получить произвольную часть ключевого потока ($binkey).

    Изучи hmac, собери hmac_md5, в качестве ключа будешь скармливать свой ключ, а в качестве данных будешь пихать uint64_t — порядковый номер блока (блоки — у тебя размером с хэш мд5) и какие то IV (начальный вектор), для усиления так сказать. IV можно свободно прикреплять к сообщению в открытом виде. На выходе hmac получишь кусок ключевого потока ($binkey), который потом и проксоришь с открытыми данными.
    Для ускорения генерации ключевого потока тебе не обязательно весь HMAC целиком пересчитывать, достаточно пересчитывать только хэш от номера блока и IV.
    В итоге будет относительно быстрый и надёжный(? в голову с ходу не приходит как быстрее брутфорса ломать) поточный шифр.
    А потом можно будет заменить md5 на что то более быстрое/интересное.

    2 nazarpc:
    А ещё лучше вообще ничего в жизни не делать, и ждать когда всё сделают другие. Только вот тогда они и получат не только люлей за ошибки но и пряников за достижения и опыт/знания.

    2lair:
    Ровно тоже самое что и с любым поточным шифром.

    2MichaelBorisov:
    У автора получилось на порядки лучше, чем многие тут пишушие/читающие могут родить за конечное время.

    2iUser:
    Ну давай, напиши в чём слабости и как ты его будешь вскрывать быстрее, чем за брутфорс.
    Твои советы по «усилению» шифра — полная фигня, хотя бы потрудился написать почему именно так ты советуешь делать, от чего это поможет и почему.

    2vilgeforce:
    Это ваши личные предубеждения против ХОР, есть куча поточных шифров и куча которые работают в поточном режиме, в том же TLS их полно.

    2StrangerInRed:
    Перестановка и подстановка — это блочный шифр.
    Поточный генерирует ключевой поток который ксорится с открытым текстом. Это именно то что сделал автор.
    Скорость брутфорса от того поточный шифр или блочный зависит НИКАК и определяется только конкретной реализацией конкретного алгоритма.


    1. lair
      16.03.2016 16:54

      Ровно тоже самое что и с любым поточным шифром.

      Вот именно поэтому в поточных шифрах используют одноразовые ключи либо nonce. Автор же об этом молчит.

      как [...] его [...] вскрывать быстрее, чем за брутфорс.

      Например, CPA позволяет полностью узнать ключевой поток для плейнтекста заданного размера (и любых меньшего). Собственно, даже KPA даст этот ключевой поток (но только для плейнтекста перехваченного (и меньших) размера). Дальше можно пытаться вычислить ключ, если хочется.


    1. StrangerInRed
      17.03.2016 10:26
      +1

      Перестановки и подстановки в поточный тоже добавляют, ваш кэп.
      Открываем https://ru.wikipedia.org/wiki/RC4 и смотрим на раунд генерации ключевого потока.


      1. Ivan_83
        17.03.2016 16:40

        Учи матчасть!

        Совершенно всё равно как генерируется ключевой поток, важно что он тупо ксорится с входными данными, те никаких подстановок и перестановок применительно к шифруемым данным не применяется. И сам ключевой поток от входных данных не зависит.
        Именно это отличает поточные шифры от блочных: для блочного шифра не существует ХОР маски для шифрования/расшифрования сообщения.

        Те если ты зашифруешь блочным шифром два сообщения одним паролем+ив+нонс, и имея открытый текст одного сообщения ты получишь ХОР маску то она при наложении на второе сообщение не расшифрует его. Именно в этом фундаментальное отличие потокового шифра от блочного.
        И оно всплывает в некоторых применениях, например XTS для дискового шифрования можно использовать только с блочными шифрами, потому что поточные дадут один ключевой поток на ВСЕ сектора.


        1. StrangerInRed
          17.03.2016 22:30

          Матчасть в том что подстановки и перестановки добавляют в раунд генерации ключевого потока. А не в сам шифр.


  1. lair
    16.03.2016 22:40

    Обновил код, теперь пароль тысячу раз хешуеться

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

    а «ключевой поток» теперь немножко сложнее

    Что от этого изменилось?

    Также есть дополнительный мастер-пароль.

    И что изменило его добавление?

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


    1. da411d
      16.03.2016 22:56

      Хех это примерно два квадриллиона вариантов.
      Но Вы не обратили внимание на код:
      $newkey = md5($key);
      for($i=0;$i<1000;$i++){
      $newkey = md5($newkey).substr(md5($newkey.$i), 6, 1);
      }
      $key = $newkey;

      строка будет не простым md5 а строкой длиной 1032 символа. Это 1032^16 комбинаций. То есть примерно полтора октиллиона. Вам этого мало?
      Ну ок, можете сделать 10 000 000 интераций! Или увеличить размер куска в substr.


      1. lair
        16.03.2016 23:08

        строка будет не простым md5 а строкой длиной 1032 символа.

        Откуда 1032, если всего 33? md5 возвращает 32, и еще один вы берете из substr. Впрочем, сам "пароль" наверняка будет меньше и легче к подбору.

        Но это все фигня на фоне many-time pad. И да, показательно, что вы проигнорировали все остальные вопросы в комментарии.


  1. funnybanana
    17.03.2016 04:29
    +1

    Злые дядьки =) Парню 17 лет, путь ещё прыгает по своим граблям =)

    P.S

    $str = str_replace('-', '+', $str);
    $str = str_replace('_', '/', $str);
    $str = base64_decode($str);
    $str = bin2hex($str);
    $str = str_split($str, 2);

    ->

    $str = str_split(bin2hex(base64_decode(str_replace(array('-', '_'), array('+', '/'), $str))), 2);