Криптология, криптография и криптоанализ

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

Задача: написать программу, которая работает с шифром Цезаря.

Это один из самых простых и известных методов шифрования. Назвали его, само собой, в честь императора Гая Юлия Цезаря, который применял его для секретной переписки с генералами.

Шифр Цезаря — это шифр подстановки: в нем каждый символ в открытом тексте заменяется на символ, который находится на некотором постоянном числе позиций левее или правее него в алфавите.

Допустим, мы устанавливаем сдвиг на 3 (ключ = 3). В таком случае А заменится на Г, Б станет Д, и так далее.

Давай для наглядности рассмотрим игрушку для шифрования кодом Цезаря:

Она состоит из двух кругов которые вращаются относительно друг друга; По периметру каждого из кругов написаны буквы в порядке алфавита. При смещении все буквы внутреннего круга смещаются на одинаковое расстояние относительно букв внешнего круга.

Для шифрования мы буквы из внешнего круга заменяем на стоящие рядом с ними буквы из нижнего круга. Например, если круги смещены как на картинке, А заменяем на С, а R - на T. А чтобы расшифровать все делаем наоборот: буквы из внутреннего круга заменяем на буквы из внешнего; то есть, С обратно на А, и Т обратно на R.

Для шифрования мы буквы из внешнего круга заменяем на стоящие рядом с ними буквы из нижнего круга. Например, если круги смещены как на картинке, А заменяем на С, а R - на T. А чтобы расшифровать все делаем наоборот: буквы из внутреннего круга заменяем на буквы из внешнего; то есть, С обратно на А, и Т обратно на R.

Допустим, песик Бобик решил написать девочке Алисе секретное сообщение. У каждого из них есть такая игрушка. Чтобы успешно зашифровать и расшифровать сообщение, они должны договориться на сколько позиций надо сместить круги своих игрушек относительно друг-друга. На картинке игрушки видно что А стала С, а В стала D, значит внутренний круг смещен на 2 позиции. Заметьте, что Y стала А (и не улетела в космос); Когда они договорились сместить круги на 2 позиции и Бобик передал Алисе надпись NGVU RNCA NGIQ, Алиса сразу поняла и пошла доставать коробку в Lego.

Так как в английском алфавите только 26 букв, можно сместить круги на 1,2,...,25 позиций. Если мы сместим на 26 (или 0) позиций то буквы на окружностях совпадут.

Это минимум теоретических данных, которые понадобятся тебе для выполнения итогового проекта. Переходим к описанию задания!

Можно почитать лекцию CS50 Криптография. Шифр Цезаря и шифр Виженера

Криптоанализ - взлом шифра

Криптография - алгоритмы шифрования данных (и не только)

Алфавит - полный набор символов, которые могут попасться в тексте, поддающемся шифровке. В шифре Цезаря важен порядок этих символов. Он может и не быть классическим порядком букв в алфавите, но его должны знать и Бобик (тот кто шифрует) и Алиса (тот кто расшифрует);

Криптоанализ статистических данных основан на том, что в каждом языке есть своя статистика использования символов. Например, в этом тексте (пока я его пишу) уже есть 14 слов “и” и 24 слова “в”. Если сохранять пробелы, то легко догадаться, что наиболее частые слова из одной буквы это скорее всего В или К или И. И если использовали шифр цезаря с классическим порядком букв в алфавите, расшифровать этот текст будет элементарно (попробуем сдвиг для В - нечитабельно -тогда попробуем сдвиг для К, и так далее).

Хорошо, можно съедать пробелы, чтобы спрятать слова из 1 буквы. Но тогда можно легко определить гласные (они встречаются намного чаще согласных), или часто встречающиеся слова вроде ИЛИ. Даже простой перебор не составит трудностей тк всего существует очень маленькое число вариантов для подбора (для английского алфавита 25). Поэтому Бобику и Алисе следует попробовать более сложные метод шифрования.

Отходить от буквального задания - можно. Главное - суть.

Результат: программа работает в нескольких режимах

Режимы:

  • Шифровка текста
  • Расшифровка текста с помощью ключа
  • Расшифровка текста с помощью brute force (перебор всех вариантов)
  • (дополнительно) Расшифровка с помощью статистического анализа текста

Программа должна открывать указанный пользователем файл с текстом и проделывать с ним одно из указанных выше действий. После этого создавать новый файл с результатом.

Программа должна выполнять следующие функции:

  • Шифровка текста из заданного файла. На вход она получает адрес файла с оригинальным текстом, адрес файла в который нужно записать зашифрованный текст, и сдвиг по алфавиту (это является ключом шифра Цезаря). Не забудьте проделать проверку того что а) файл оригинала по заданному адресу существует, и б) ключ от 0 и до (размер алфавита - 1) (или можете взять остаток от деления на размер алфавита).
  • Расшифровка при известном ключе. На входе - адрес зашифрованного файла и адрес куда писать расшифрованный файл, а также сдвиг по алфавиту который использовался при шифровании (ключ).
  • Расшифровка методом brute force (перебором всех возможных сдвигов); На входе - адрес зашифрованного файла, (опционально) адрес файла с текстом который является примером текста что был зашифрован (например другой труд того же автора) и адрес файла который должен содержать расшифрованный текст.
  • Расшифровка методом статистического анализа; На входе тоже самое что и для расшифровке перебором.

Не забудьте проделать валидацию входных данных.

Исходный текст для шифрования должен быть в файле. Желательно в формате txt. программа должна уметь работать с большими текстами на сотни страниц. Этот файл программа должна уметь зашифровать и записать зашифрованный текст в другой файл.

Алфавит

Создай алфавит, в котором существует задача. По условию это русский алфавит и пунктуация. , ” ’ : - ! ? ПРОБЕЛ Не забываем про пробел!

Как это можно сделать? Например, можно загнать алфавит в String, несколько строк или массив String

Можно загнать алфавит во множество Set, в массив или List. А ещё можно воспользоваться таблицей ASCII.

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


private static final List ALPHABET = Arrays.asList('а', 'б', 'в',
'г', 'д', 'е', 'ж', 'з', 'и', 'к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у',
'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'э', 'я', '.', ',', '«', '»',
':', '!', '?', ' ');

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


private static final char[] ALPHABET = {'а', 'б', 'в', 'г', 'д', 'е', 'ж', 'з',
'и','к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ',
'ъ', 'ы', 'ь', 'э', 'я', '.', ',', '«', '»', '"', '\'', ':', '!', '?', ' '};

Шифровка

Для нее нужно знать сдвиг (ключ) и алфавит.

Для каждого символа оригинального текста нужно -

  • проверить что он есть в вашем алфавите. Если его нет, пропускаем этот символ.
  • найти его позицию в алфавите. Подумайте, какую структуру данных нужно использовать чтобы ускорить этот процесс (раз в 15), ведь необязательно же сканировать всю библиотеку в поисках книги на букву Ы (Ладно, П).
  • найти символ на позиции смещенной на заданный сдвиг. И помним что в примере с игрушкой Y стала А (и не улетела в космос). Как это гарантировать? (можно сделать (позиция буквы + сдвиг) %( размер алфавита). Процент - оператор получения остатка от деления).
  • заменить оригинальный символ на зашифрованный

Сохранить результат в файл (чтобы избежать плохого пользователя который попробует зафигачить тебе .bash_profile или hosts валидируй имя файла вывода!)

Создать интерфейс программы / меню пользователя

Графический интерфейс можно создать с помощью JavaFX или Swing. Желательно этим заморочиться уже после создания основной программы. Однако если не успеваешь или не хочешь тратить на это время, можно создать простое текстовое меню и вывести его в консоль. Например:


import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Parameters;
import picocli.CommandLine.Option;
import picocli.CommandLine.ParameterException;
import picocli.CommandLine.Spec;
import java.util.Locale;
import java.io.File;

@Command(name = "cypher", subcommands = {CommandLine.HelpCommand.class }, 
         description = "Caesar cypher command")
public class Cypher implements Runnable { 
    @Spec CommandSpec spec;
    @Command(name = "encrypt", description = "Encrypt from file to file using key") 
    void encrypt(
                 @Parameters(paramLabel = "", description = "source file with text to encrypt") File src, 
                 @Parameters(paramLabel = "", description = "dest file which should have encrypted text") File dest, 
                 @Parameters(paramLabel = "", description = "key for encryption") int key) {
        // TODO
    }
    
    @Command(name = "brute force", description = "Decrypt from file to file using brute force") // |3|
    void bruteForce(
                 @Parameters(paramLabel = "", description = "source file with encrypted text") File src, 
                 @Option(names = {"-r", "--representative"}, description = "file with unencrypted representative text") File representativeFile, 
                 @Parameters(paramLabel = "", description = "dest file which should have decrypted text") File dest) {
        // TODO
    }

    @Command(name = "statistical decryption", description = "Decrypt from file to file using statistical analysis") // |3|
    void statisticalDecrypt(
                 @Parameters(paramLabel = "", description = "source file with encrypted text") File src, 
                 @Option(names = {"-r", "--representative"}, description = "file with unencrypted representative text") File representativeFile, 
                 @Parameters(paramLabel = "", description = "dest file which should have decrypted text") File dest) {
        // TODO
    }
    

    @Command(name = "decrypt", description = "Decrypt from file to file using statistical analysis") // |3|
    void decrypt(
                 @Parameters(paramLabel = "", description = "source file with encrypted text") File src, 
                 @Parameters(paramLabel = "", description = "dest file which should have decrypted text") File dest,
                 @Parameters(paramLabel = "", description = "key for encryption") int key) {
        // TODO
    }
    
    @Override
    public void run() {
        throw new ParameterException(spec.commandLine(), "Specify a subcommand");
    }

    public static void main(String[] args) {
        int exitCode = new CommandLine(new Cypher()).execute(args);
        System.exit(exitCode);
    }
}

Если не хочешь разбираться с Picocli, можешь просто сделать меню с помощью циклов или оператора switch. Также нужно, чтобы программа завершала работу по желанию пользователя (например, вводом слова “exit”). А теперь, немного подробнее по каждой из подзадач.

Расшифровка

Для нее нужно знать сдвиг (ключ) и алфавит.

Для каждого символа зашифрованного текста нужно -

  • проверить что он есть в вашем алфавите. Если нет - вас ломают хакеры. Паникуйте (ну или верните ошибку).
  • найти его позицию в алфавите.
  • найти символ на позиции смещенной на заданный сдвиг (но только помните - вы не пытаетесь еще раз зашифровать шифр - поэтому сдвигаем в другую сторону).
  • заменить зашифрованный символ на расшифрованный

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

Сохранить результат в файл.

Взлом (Brute Force)

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

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

Взлом (Статистический анализ)

Дополнительное требования(опционально)

Используйте пример текста (репрезентативный текст автора или в том же стиле) и составьте статистику букв (например, как часто встречается на каждые 1000 символов); (Кстати. Легко взломать шифр и без такого файла и анализа - попробуйте угадать пробел - это наверняка наиболее часто встречающийся символ в обычном тексте.)

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

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

Разбор одного из вариантов готового проекта

Проект проверяется во время прохождения его группой