воскресенье, 27 декабря 2015 г.

Шифрование текста с помощью операции XOR

Вспомним таблицу истинности для данной операции:
x y out
0 0 0
0 1 1
1 0 1
1 1 0

Т.е. если количество единиц нечётно, то получаем истину, в противном
случае ложь. Используя данный факт, можно восстановить исходное сообщение.
Допустим выше x - это текст, y - это ключ, out - зашифрованное сообщение.
Таким образом, если применить к out повторную операцию с ключом y, то
можно получить исходный x.
y out x 
0 0   0 
1 1   0 
0 1   1 
1 0   1 

Теперь рабочая реализация данного принципа.

Шаг 1


Подготовка.
#!/usr/bin/perl 

use strict;
use warnings;
use utf8;
use open ':std', ":encoding(utf-8)"; # избавляемся от предупреждений
                                     # связанных с использование Юникода
                                     # при выводе на экран.
$\="\n";   # позволит нам использовать print "foo", 
           # вместо print "foo\n", аналог say.

 

Шаг 2


Задаём текст, который будет зашифрован и ключ, с помощью которого это
будет выполнено. Ключ и текст возьмём из двух весьма известных книг.

my $string = <<'EOF';
Свобода, свободный ум и наука заведут их в такие дебри и поставят пред
такими чудами и неразрешимыми тайнами, что одни из них, непокорные и
свирепые, истребят себя самих, другие, непокорные, но малосильные,
истребят друг друга, а третьи, оставшиеся, слабосильные и несчастные,
приползут к ногам нашим и возопиют к нам...
EOF

my $key = 'Кто затыкает ухо свое от вопля бедного, тот и сам 
будет вопить, — и не будет услышан.';

 

Шаг 3


Добавляем функцию дешифровки, которая по сути являеется синонимом для функции encode (см Шаг 4), с тем лишь отличием, что функция encode вызывается с еще одним дополнительным аргументом. Подробнее про goto.

sub decode { $_[2] = 1; goto &encode; }

 

Шаг 4


Добавляем каркас функции для шифрования текста.
sub encode {
    my $string    = shift;
    my $key       = shift;
    my $is_decode = shift;

    my $processed_string = '';

    # Шаг 5.
    # Шаг 6.

    return $processed_string;
}

 

Шаг 5


my $string_unpacked = $is_decode ? $string : unpack('U0B*', $string);
my $key_unpacked    = unpack('U0B*', $key); 

Вспомним работу недооцененных функций pack и unpack.

Задача функции unpack - "расщепить" что-то, что идёт вторым аргументом на "части", вид которых описан в первом аргументе.
Задача же функции pack - "собрать" что-то новое из частей второго аргумента по чертежам, описанным в первом аргументе.

В данном месте, когда $is_decode - истино, а это происходит при дешифровки, при вызове функции decode, используется такой вызов unpack: unpack('U0B*', $string). Что он означает? Как сказано выше, unpack будет расщеплять строку $string на части, по схеме 'U0B*', которая обозначает:

U0 - показатель того, что имеем дело с Юникодом; поэтому unpack сможет правильно обработать символы Юникода, которые могут занимать более одного байта.
B* - непосредственно сама схема "разбивки" на части, которая говорит о том, что необходимо расщепить всю (знак * говорит об этом) строку $string на биты с убывающим порядком в каждом байте.

В итоге и $string_unpacked и $key_unpacked будут содержать строки, состоящие из нулей и единиц.

Памятка! pack и unpack обрабатывают строки, поэтому будьте внимательны, когда обрабатываете с их помощью числа. Например unpack("B*", 3) выдаст 00110011, так как воспринимает второй аргумент как строку "3", а не как число. Поэтому, чтобы получить то, что вам нужно, необходимо предварительно "упаковать" строку "3" в число 3, а уже затем обрабатывать это значение, например так: unpack("B*", pack("C*", 3)) что выдаст нам 00000011.

Шаг 6


Разбиваем $string_unpacked на куски, равные длине $key_unpacked.
unpack("(a$key_length)*", $string_unpacked) - эта запись говорит о том, что необходимо просто разбить строку $string_unpacked на куски (использование скобок () позволяет группировать правила в первом  аргументе, что даёт дополнительну гибкость в обработке), длиной $key_length, без каких либо преобразований, об этом говорит символ "а". Затем каждый такой кусок, будет подвержен обработке в цикле (см Шаг 7,8)

my $key_length = length $key_unpacked;

for my $part (unpack("(a$key_length)*", $string_unpacked)) {
    # Шаг 7.
    # Шаг 8.
}

 

Шаг 7


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

my $a = pack('B*', $part);
my $b = pack('B*', $key_unpacked);
my $c = $a ^ $b;

 

Шаг 8


Полученный результат распаковываем обратно в строку вида "0100110..." и добавляем её
к общему результату.

my $unpacked_c = unpack('B*', $c);

$processed_string .= $unpacked_c;

 

Шаг 9


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

my $cipher = encode($string, $key);

my $orig = decode($cipher, $key);

print pack('U0B*', $orig); 

И всё вместе

 #!/usr/bin/perl 

use strict;
use warnings;
use utf8;
use open ':std', ":encoding(utf-8)";
$\="\n";

my $string = <<'EOF';
Свобода, свободный ум и наука заведут их в такие дебри и поставят пред такими 
чудами и неразрешимыми тайнами, что одни из них, непокорные и свирепые, истребят 
себя самих, другие, непокорные, но малосильные, истребят друг друга, а третьи, 
оставшиеся, слабосильные и несчастные, приползут к ногам нашим и возопиют 
к нам...
EOF

my $key = 'Кто затыкает ухо свое от вопля бедного, тот и сам '.
    'будет вопить, — и не будет услышан.';

sub encode {
    my $string    = shift;
    my $key       = shift;
    my $is_decode = shift;

    my $processed_string = '';

    my $string_unpacked = $is_decode ? $string : unpack('U0B*', $string);

    my $key_unpacked = unpack('U0B*', $key);

    my $key_length = length $key_unpacked;

    for my $part (unpack("(a$key_length)*", $string_unpacked)) {
        my $a = pack('B*', $part);
        my $b = pack('B*', $key_unpacked);
        my $c = $a ^ $b;
        my $unpacked_c = unpack('B*', $c);

        $processed_string .= $unpacked_c;
    } 

    return $processed_string;
}

sub decode { $_[2] = 1; goto &encode; }

my $cipher = encode($string, $key);
my $orig = decode($cipher, $key);

print pack('U0B*', $orig);

Комментариев нет:

Отправить комментарий