diff --git a/src/main/java/com/javarush/orlov/BruteForce.java b/src/main/java/com/javarush/orlov/BruteForce.java new file mode 100644 index 0000000..954c88e --- /dev/null +++ b/src/main/java/com/javarush/orlov/BruteForce.java @@ -0,0 +1,64 @@ +package com.javarush.orlov; + +import java.util.ArrayList; + +public class BruteForce extends Encrypt { + + // Мини-словарь частых слов для проверки корректности расшифровки + private static final String[] DICTIONARY = {"и", "в", "на", "что", "как", "это", "он", "она", "они"}; + + // Метод перебирает все возможные ключи шифра Цезаря и выбирает лучший + // Лучший ключ определяется по количеству слов из словаря, найденных в тексте + public ArrayList bruteForce(ArrayList text) { + ArrayList result; + int key = 0; // ключ с наибольшим количеством совпадений + int count = 0; // максимальное количество совпадений слов из словаря + + // Перебираем все возможные ключи (все позиции алфавита) + for (int i = 0; i < ALPHABET.length; i++) { + result = super.encrypt(text, i); // "расшифровываем" текст с текущим ключом + int temp = extracted(result); // подсчитываем совпадения слов из словаря + if (temp > count) { + key = i; // если найдено больше совпадений, сохраняем ключ + count = temp; + } + } + + // Возвращаем текст, расшифрованный с лучшим ключом + return super.encrypt(text, key); + } + + // Метод подсчитывает количество слов из словаря в списке строк + private static int extracted(ArrayList result) { + int count = 0; + + // Перебираем все строки текста + for (int j = 0; j < result.size(); j++) { + count += checkWord(result.get(j)); // считаем совпадения слов из словаря в каждой строке + } + + return count; + } + + // Метод проверяет строку на наличие слов из словаря + private static int checkWord(String str) { + int count = 0; + + // Разбиваем строку на слова по пробелам + String[] word = str.split(" "); + + for (int i = 0; i < word.length; i++) { + // Убираем все символы кроме букв + word[i] = word[i].replaceAll("[^а-яА-Я]", ""); + + // Сравниваем слово с каждым словом из словаря (игнорируя регистр) + for (int j = 0; j < DICTIONARY.length; j++) { + if (word[i].equalsIgnoreCase(DICTIONARY[j])) { + count++; // если совпадение найдено, увеличиваем счетчик + } + } + } + + return count; + } +} diff --git a/src/main/java/com/javarush/orlov/CaesarCipherApp.java b/src/main/java/com/javarush/orlov/CaesarCipherApp.java new file mode 100644 index 0000000..7db02f9 --- /dev/null +++ b/src/main/java/com/javarush/orlov/CaesarCipherApp.java @@ -0,0 +1,105 @@ +package com.javarush.orlov; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +public class CaesarCipherApp { + + public static void main(String[] args) throws IOException { + + // Главный цикл программы, пока пользователь не выберет выход + while (true) { + int num = Validator.begin(); // Получаем выбор пользователя + + switch (num) { + case 0: + System.out.println("Выход из программы. Пока!"); + return; + + case 1: + encryptFile(); // Шифруем файл + System.out.println("Готово!"); + break; + + case 2: + decryptFile(); // Расшифровываем файл по ключу + System.out.println("Готово!"); + break; + + case 3: + bruteForceFile(); // Расшифровка методом полного перебора + System.out.println("Готово!"); + break; + + case 4: + statisticalFile(); // Расшифровка статистическим анализом + System.out.println("Готово!"); + break; + + default: + System.out.println("Некорректный выбор, попробуйте снова."); + } + } + } + + // Метод для шифрования текста из файла + private static void encryptFile() throws IOException { + int key = Validator.scanKey(); // Считываем ключ от пользователя + + // Читаем текст из файла + ArrayList text = new ArrayList<>(Files.readAllLines(Path.of("text\\text.txt"))); + + // Создаём новый файл для зашифрованного текста + FileService.createNewFile("text\\encrypted.txt"); + + // Шифруем текст с указанным ключом + Encrypt encrypt = new Encrypt(); + ArrayList result = new ArrayList<>(encrypt.encrypt(text, key)); + + // Записываем результат в файл + FileService.writeFile("text\\encrypted.txt", result); + } + + // Метод для расшифровки текста по ключу + private static void decryptFile() throws IOException { + int key = Validator.scanKey(); // Считываем ключ от пользователя + + // Читаем зашифрованный текст + ArrayList text = new ArrayList<>(Files.readAllLines(Path.of("text\\encrypted.txt"))); + + // Создаём файл для расшифрованного текста + FileService.createNewFile("text\\decrypt.txt"); + + // Расшифровываем текст + Decrypt decrypt = new Decrypt(); + FileService.writeFile("text\\decrypt.txt", decrypt.decrypt(text, key)); + } + + // Метод для расшифровки текста методом полного перебора (Brute Force) + private static void bruteForceFile() throws IOException { + ArrayList text = new ArrayList<>(Files.readAllLines(Path.of("text\\encrypted.txt"))); + + // Создаём файл для результата + FileService.createNewFile("text\\bruteForce.txt"); + + BruteForce bruteForce = new BruteForce(); + + // Записываем расшифрованный текст с найденным ключом + FileService.writeFile("text\\bruteForce.txt", bruteForce.bruteForce(text)); + } + + // Метод для расшифровки текста статистическим анализом + private static void statisticalFile() throws IOException { + ArrayList text = new ArrayList<>(Files.readAllLines(Path.of("text\\encrypted.txt"))); + + // Создаём файл для результата + FileService.createNewFile("text\\statisticalAnalysis.txt"); + + StatisticalAnalysis statisticalAnalysis = new StatisticalAnalysis(); + + // Записываем расшифрованный текст + FileService.writeFile("text\\statisticalAnalysis.txt", statisticalAnalysis.statisticalAnalysis(text)); + } +} diff --git a/src/main/java/com/javarush/orlov/Decrypt.java b/src/main/java/com/javarush/orlov/Decrypt.java new file mode 100644 index 0000000..97514ef --- /dev/null +++ b/src/main/java/com/javarush/orlov/Decrypt.java @@ -0,0 +1,18 @@ +package com.javarush.orlov; + +import java.util.ArrayList; + +public class Decrypt { + + // Метод расшифровывает текст, используя ключ шифра Цезаря + // Для расшифровки используется метод шифрования с отрицательным ключом + public ArrayList decrypt(ArrayList text, int key) { + + // Создаём объект класса Encrypt для работы с шифром Цезаря + Encrypt encrypt = new Encrypt(); + + // Возвращаем результат шифрования с отрицательным ключом, + // что фактически выполняет расшифровку + return encrypt.encrypt(text, -key); + } +} diff --git a/src/main/java/com/javarush/orlov/Encrypt.java b/src/main/java/com/javarush/orlov/Encrypt.java new file mode 100644 index 0000000..1eac8da --- /dev/null +++ b/src/main/java/com/javarush/orlov/Encrypt.java @@ -0,0 +1,62 @@ +package com.javarush.orlov; + +import java.util.ArrayList; + +public class Encrypt { + + // Алфавит, используемый для шифрования и дешифрования + // Содержит русские буквы в нижнем и верхнем регистре, знаки препинания и пробел + public static final char[] ALPHABET = {'а', 'б', 'в', 'г', 'д', 'е', 'ё', 'ж', 'з', + 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', + 'ъ', 'ы', 'ь', 'э', 'ю', 'я', '.', ',', '«', '»', '"', '\'', ':', '!', '?', ' ', + 'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ё', 'Ж', 'З', 'И', 'Й', 'К', 'Л', 'М', 'Н', 'О', 'П', + 'Р', 'С', 'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ъ', 'Ы', 'Ь', 'Э', 'Ю', 'Я'}; + + // Шифрует или расшифровывает текст с помощью ключа + // Если ключ положительный — шифрование, если отрицательный — расшифровка + public ArrayList encrypt(ArrayList text, int key) { + + ArrayList result = new ArrayList<>(); + + // Обрабатываем каждую строку текста + for (String s : text) { + result.add(checkAlphabet(s, key)); + } + + return result; + } + + // Проверяет каждый символ строки и сдвигает его по алфавиту + private String checkAlphabet(String text, int key) { + StringBuilder builder = new StringBuilder(); + + // Перебираем символы строки + for (int k = 0; k < text.length(); k++) { + char current = text.charAt(k); + + // Проверяем, есть ли символ в алфавите + boolean found = isFound(key, current, builder); + + // Если символа нет в алфавите — оставляем его без изменений + if (!found) { + builder.append(current); + } + } + + return builder.toString(); + } + + // Сдвигает символ по алфавиту на заданное количество позиций + // Возвращает true, если символ найден в алфавите, иначе false + private boolean isFound(int key, char current, StringBuilder builder) { + for (int j = 0; j < ALPHABET.length; j++) { // перебор всех символов алфавита + if (current == ALPHABET[j]) { + // Сдвиг с учетом длины алфавита и обработки отрицательного ключа + builder.append(ALPHABET[(j + key % ALPHABET.length + ALPHABET.length) % ALPHABET.length]); + return true; + } + } + return false; + } + +} diff --git a/src/main/java/com/javarush/orlov/FileService.java b/src/main/java/com/javarush/orlov/FileService.java new file mode 100644 index 0000000..06d3076 --- /dev/null +++ b/src/main/java/com/javarush/orlov/FileService.java @@ -0,0 +1,49 @@ +package com.javarush.orlov; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; + +public class FileService { + + // Метод для чтения содержимого файла в виде строки + public static String readFile(String filePath) throws IOException { + Path path = Paths.get(filePath); // получаем путь к файлу + byte[] bytes = Files.readAllBytes(path); // читаем все байты из файла + return new String(bytes, StandardCharsets.UTF_8); // преобразуем байты в строку с кодировкой UTF-8 + } + + // Метод для создания нового файла + // Если файл уже существует — удаляет его и создаёт новый + public static void createNewFile(String filePath) throws IOException { + Path path = Path.of(filePath); // путь к файлу + + if (Files.exists(path)) { // проверяем, существует ли файл + Files.delete(path); // если да — удаляем + } + Files.createFile(path); // создаём новый файл + } + + // Метод для записи текста в файл + public static void writeFile(String filePath, ArrayList str) { + Path path = Paths.get(filePath); // путь к файлу + try { + StringBuilder builder = new StringBuilder(); + + // Объединяем все строки с переносами + for (String line : str) { + builder.append(line).append(System.lineSeparator()); + } + + // Запись в файл с явным указанием кодировки UTF-8 + Files.writeString(path, builder.toString(), StandardCharsets.UTF_8); + + } catch (IOException e) { + // Вывод ошибки, если запись не удалась + System.out.println("Ошибка записи файла: " + e.getMessage()); + } + } +} diff --git a/src/main/java/com/javarush/orlov/StatisticalAnalysis.java b/src/main/java/com/javarush/orlov/StatisticalAnalysis.java new file mode 100644 index 0000000..4507c5c --- /dev/null +++ b/src/main/java/com/javarush/orlov/StatisticalAnalysis.java @@ -0,0 +1,85 @@ +package com.javarush.orlov; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class StatisticalAnalysis { + + // Выполняет статистический анализ текста для взлома шифра Цезаря + // Объединяет весь текст в одну строку, находит самый частый символ и расшифровывает текст + public ArrayList statisticalAnalysis(ArrayList text) { + ArrayList result = new ArrayList<>(); + + // Объединяем все строки текста в одну для анализа частоты символов + StringBuilder allText = new StringBuilder(); + for (String line : text) { + allText.append(line); + } + + // Находим самый часто встречающийся символ во всём тексте + char top = word(allText.toString()); + + // Вычисляем ключ шифра, предполагая, что самый частый символ — пробел + int key = differenceWithSpace(top); + + // Расшифровываем текст с найденным ключом + Decrypt decrypt = new Decrypt(); + result = decrypt.decrypt(text, key); + + return result; + } + + // Находит символ, который встречается чаще всего в строке + private char word(String line) { + HashMap count = new HashMap<>(); + + // Подсчитываем количество каждого символа + for (int j = 0; j < line.length(); j++) { + char c = line.charAt(j); + count.put(c, count.getOrDefault(c, 0) + 1); + } + + char mostFrequent = ' '; + int maxCount = 0; + + // Определяем символ с максимальной частотой + for (Map.Entry entry : count.entrySet()) { + if (entry.getValue() > maxCount) { + maxCount = entry.getValue(); + mostFrequent = entry.getKey(); + } + } + + return mostFrequent; + } + + // Вычисляет ключ шифра Цезаря как разницу позиции самого частого символа и пробела + // Если ключ отрицательный, корректирует его, добавляя длину алфавита + public static int differenceWithSpace(char mostFrequent) { + int indexMostFrequent = -1; + int indexSpace = -1; + + // Находим позиции символов в алфавите + for (int i = 0; i < Encrypt.ALPHABET.length; i++) { + if (Encrypt.ALPHABET[i] == mostFrequent) { + indexMostFrequent = i; + } + if (Encrypt.ALPHABET[i] == ' ') { + indexSpace = i; + } + } + + // Если символы не найдены, выбрасываем исключение + if (indexMostFrequent == -1 || indexSpace == -1) { + throw new IllegalArgumentException("Символ не найден в алфавите"); + } + + // Вычисляем ключ + int key = indexMostFrequent - indexSpace; + if (key < 0) key += Encrypt.ALPHABET.length; // корректировка отрицательного ключа + + return key; + } + +} diff --git a/src/main/java/com/javarush/orlov/Validator.java b/src/main/java/com/javarush/orlov/Validator.java new file mode 100644 index 0000000..b4da170 --- /dev/null +++ b/src/main/java/com/javarush/orlov/Validator.java @@ -0,0 +1,59 @@ +package com.javarush.orlov; + +import java.util.InputMismatchException; +import java.util.Scanner; + +public class Validator { + + // Разделитель для визуального оформления меню + private static final String asterisk = "*".repeat(60); + + // Метод отображает главное меню программы и возвращает выбор пользователя + public static int begin() { + Scanner scanner = new Scanner(System.in); + System.out.println("Привет! Это программа для работы с шифром Цезаря \n"); + + while (true) { + // Вывод меню + System.out.println(asterisk + " Выберите: \n" + "1. Зашифровать файл \n" + "2. Расшифровать файл \n" + "3. Взлом (Brute Force) \n" + "4. Взлом (Статистический анализ) \n" + "0. Выход "); + + short num; + + while (true) { + try { + num = scanner.nextShort(); // считываем выбор пользователя + + // Проверяем корректность ввода + if (num >= 0 && num <= 4) return num; + else { + System.out.println("Некорректный ввод. Нужно выбрать число от 0 до 4."); + } + + } catch (InputMismatchException e) { + // Если введено не число, выводим сообщение и очищаем ввод + System.out.println("Нужно ввести цифру от 0 до 4!"); + scanner.next(); // очищаем некорректный ввод + } + + // Повторный вывод меню + System.out.println("Выберите: \n" + "1. Зашифровать файл \n" + "2. Расшифровать файл \n" + "3. Взлом (Brute Force) \n" + "4. Взлом (Статистический анализ) \n" + "0. Выход"); + } + } + } + + // Метод запрашивает у пользователя ключ для шифра Цезаря + public static int scanKey() { + Scanner scanner = new Scanner(System.in); + + while (true) { + try { + System.out.println("Введите ключ:"); + return scanner.nextInt(); // возвращаем введённый ключ + } catch (InputMismatchException e) { + // Если введено не число, выводим сообщение и очищаем ввод + System.out.println("Нужно ввести цифру!!"); + scanner.next(); // очищаем некорректный ввод + } + } + } +}