diff --git a/src/main/java/com/javarush/timoshenko/MainApp.java b/src/main/java/com/javarush/timoshenko/MainApp.java new file mode 100644 index 0000000..75bec65 --- /dev/null +++ b/src/main/java/com/javarush/timoshenko/MainApp.java @@ -0,0 +1,219 @@ +package com.javarush.timoshenko; + +import com.javarush.timoshenko.core.*; +import com.javarush.timoshenko.exception.CipherException; +import java.util.Scanner; + +public class MainApp { + private FileManager fileManager; + private Validator validator; + private CaesarCipher cipher; + private BruteForce bruteForce; + private StatisticalAnalyzer statisticalAnalyzer; + private Scanner scanner; + private char[] currentAlphabet; + + public MainApp() { + scanner = new Scanner(System.in); + selectAlphabet(); + initializeComponents(); + } + + private void selectAlphabet() { + System.out.println("=== Выбор алфавита ==="); + System.out.println("1. Русский алфавит"); + System.out.println("2. Английский алфавит"); + + int choice = getIntInput("Выберите алфавит: "); + + switch (choice) { + case 1 -> currentAlphabet = CaesarCipher.RUSSIAN_ALPHABET; + case 2 -> currentAlphabet = CaesarCipher.ENGLISH_ALPHABET; + default -> { + System.out.println("Неверный выбор. Используется русский алфавит."); + currentAlphabet = CaesarCipher.RUSSIAN_ALPHABET; + } + } + + System.out.println("Выбран алфавит: " + + (currentAlphabet == CaesarCipher.RUSSIAN_ALPHABET ? "Русский" : "Английский")); + } + + private void initializeComponents() { + fileManager = new FileManager(); + validator = new Validator(currentAlphabet); + cipher = new CaesarCipher(currentAlphabet); + bruteForce = new BruteForce(cipher); + statisticalAnalyzer = new StatisticalAnalyzer(cipher); + } + + public void run() { + while (true) { + printMenu(); + int choice = getIntInput("Выберите опцию: "); + + switch (choice) { + case 1 -> encrypt(); + case 2 -> decryptWithKey(); + case 3 -> bruteForceDecrypt(); + case 4 -> statisticalAnalysis(); + case 5 -> changeAlphabet(); + case 0 -> { + System.out.println("Выход из программы."); + return; + } + default -> System.out.println("Неверный выбор. Попробуйте снова."); + } + } + } + + private void printMenu() { + System.out.println("\n=== Шифр Цезаря ==="); + System.out.println("Алфавит: " + + (currentAlphabet == CaesarCipher.RUSSIAN_ALPHABET ? "Русский" : "Английский")); + System.out.println("1. Зашифровать текст"); + System.out.println("2. Расшифровать текст с ключом"); + System.out.println("3. Взлом brute force"); + System.out.println("4. Статистический анализ"); + System.out.println("5. Сменить алфавит"); + System.out.println("0. Выход"); + } + + private void encrypt() { + try { + String inputFile = getFileInput("Введите путь к исходному файлу: ", false); + String outputFile = getFileInput("Введите путь для сохранения результата: ", true); + int key = getIntInput("Введите ключ (число от 0 до " + (currentAlphabet.length - 1) + "): "); + + if (!validator.isValidKey(key)) { + System.out.println("Неверный ключ!"); + return; + } + + String text = fileManager.readFile(inputFile); + String encrypted = cipher.encrypt(text, key); + fileManager.writeFile(encrypted, outputFile); + + System.out.println("Текст успешно зашифрован и сохранен в: " + outputFile); + + } catch (CipherException e) { + System.out.println("Ошибка при шифровании: " + e.getMessage()); + } + } + + private void decryptWithKey() { + try { + String inputFile = getFileInput("Введите путь к зашифрованному файлу: ", false); + String outputFile = getFileInput("Введите путь для сохранения результата: ", true); + int key = getIntInput("Введите ключ: "); + + if (!validator.isValidKey(key)) { + System.out.println("Неверный ключ!"); + return; + } + + String text = fileManager.readFile(inputFile); + String decrypted = cipher.decrypt(text, key); + fileManager.writeFile(decrypted, outputFile); + + System.out.println("Текст успешно расшифрован и сохранен в: " + outputFile); + + } catch (CipherException e) { + System.out.println("Ошибка при расшифровке: " + e.getMessage()); + } + } + + private void bruteForceDecrypt() { + try { + String inputFile = getFileInput("Введите путь к зашифрованному файлу: ", false); + String sampleFile = getFileInput("Введите путь к файлу-образцу (или Enter чтобы пропустить): ", true); + String outputFile = getFileInput("Введите путь для сохранения результата: ", true); + + String encryptedText = fileManager.readFile(inputFile); + String sampleText = ""; + + if (!sampleFile.isEmpty() && validator.isFileExists(sampleFile)) { + sampleText = fileManager.readFile(sampleFile); + } + + String decrypted = bruteForce.decryptByBruteForce(encryptedText, sampleText); + fileManager.writeFile(decrypted, outputFile); + + System.out.println("Brute force завершен. Результат сохранен в: " + outputFile); + + } catch (CipherException e) { + System.out.println("Ошибка при brute force: " + e.getMessage()); + } + } + + private void statisticalAnalysis() { + try { + String inputFile = getFileInput("Введите путь к зашифрованному файлу: ", false); + String sampleFile = getFileInput("Введите путь к файлу-образцу: ", false); + String outputFile = getFileInput("Введите путь для сохранения результата: ", true); + + String encryptedText = fileManager.readFile(inputFile); + String sampleText = fileManager.readFile(sampleFile); + + int shift = statisticalAnalyzer.findMostLikelyShift(encryptedText, sampleText); + String decrypted = cipher.decrypt(encryptedText, shift); + + fileManager.writeFile(decrypted, outputFile); + + System.out.println("Статистический анализ завершен. Найденный ключ: " + shift); + System.out.println("Результат сохранен в: " + outputFile); + + } catch (CipherException e) { + System.out.println("Ошибка при статистическом анализе: " + e.getMessage()); + } + } + + private void changeAlphabet() { + selectAlphabet(); + initializeComponents(); + System.out.println("Алфавит изменен."); + } + + private String getFileInput(String prompt, boolean canBeEmpty) { + while (true) { + System.out.print(prompt); + String input = scanner.nextLine().trim(); + + if (input.isEmpty() && canBeEmpty) { + return input; + } + + if (input.isEmpty() && !canBeEmpty) { + System.out.println("Путь не может быть пустым."); + continue; + } + + if (!canBeEmpty && !validator.isFileExists(input)) { + System.out.println("Файл не существует: " + input); + continue; + } + + if (canBeEmpty && !input.isEmpty() && !validator.isValidOutputPath(input)) { + System.out.println("Невозможно записать в указанный путь: " + input); + continue; + } + + return input; + } + } + + private int getIntInput(String prompt) { + while (true) { + System.out.print(prompt); + try { + return Integer.parseInt(scanner.nextLine().trim()); + } catch (NumberFormatException e) { + System.out.println("Пожалуйста, введите целое число."); + } + } + } + + public static void main(String[] args) { + new MainApp().run(); + } +} diff --git a/src/main/java/com/javarush/timoshenko/core/BruteForce.java b/src/main/java/com/javarush/timoshenko/core/BruteForce.java new file mode 100644 index 0000000..4b93a14 --- /dev/null +++ b/src/main/java/com/javarush/timoshenko/core/BruteForce.java @@ -0,0 +1,119 @@ +package com.javarush.timoshenko.core; + +public class BruteForce { + private final CaesarCipher cipher; + + public BruteForce(CaesarCipher cipher) { + this.cipher = cipher; + } + + public String decryptByBruteForce(String encryptedText, String sampleText) { + int bestKey = 0; + double bestMatch = 0; + char[] alphabet = cipher.getAlphabet(); + + + for (int key = 0; key < alphabet.length; key++) { + String decrypted = cipher.decrypt(encryptedText, key); + double matchScore = calculateMatchScore(decrypted, sampleText); + + if (matchScore > bestMatch) { + bestMatch = matchScore; + bestKey = key; + } + } + + return cipher.decrypt(encryptedText, bestKey); + } + + private double calculateMatchScore(String text, String sampleText) { + if (sampleText == null || sampleText.isEmpty()) { + + return calculateLetterFrequency(text); + } + + + return calculateFrequencySimilarity(text, sampleText); + } + + private double calculateLetterFrequency(String text) { + char[] alphabet = cipher.getAlphabet(); + boolean isRussian = alphabet == CaesarCipher.RUSSIAN_ALPHABET; + + + double[] expectedFrequencies; + int letterCount; + + if (isRussian) { + expectedFrequencies = new double[]{ + 0.0801, 0.0159, 0.0454, 0.0170, 0.0298, 0.0845, 0.0094, 0.0165, 0.0735, + 0.0121, 0.0349, 0.0440, 0.0321, 0.0670, 0.1097, 0.0281, 0.0473, 0.0547, + 0.0626, 0.0262, 0.0026, 0.0097, 0.0048, 0.0144, 0.0073, 0.0036, 0.0004, + 0.0190, 0.0174, 0.0032, 0.0064, 0.0201 + }; + letterCount = 32; + } else { + expectedFrequencies = new double[]{ + 0.0812, 0.0149, 0.0271, 0.0432, 0.1202, 0.0230, 0.0203, 0.0592, 0.0731, + 0.0010, 0.0069, 0.0398, 0.0261, 0.0695, 0.0768, 0.0182, 0.0011, 0.0602, + 0.0628, 0.0910, 0.0288, 0.0111, 0.0209, 0.0017, 0.0211, 0.0007 + }; + letterCount = 26; + } + + int[] actualCounts = new int[letterCount]; + int totalLetters = 0; + + for (char c : text.toLowerCase().toCharArray()) { + for (int i = 0; i < letterCount; i++) { + if (c == alphabet[i]) { + actualCounts[i]++; + totalLetters++; + break; + } + } + } + + if (totalLetters == 0) return 0; + + double correlation = 0; + for (int i = 0; i < letterCount; i++) { + double actualFrequency = (double) actualCounts[i] / totalLetters; + correlation += Math.min(actualFrequency, expectedFrequencies[i]); + } + + return correlation; + } + + private double calculateFrequencySimilarity(String text, String sample) { + char[] alphabet = cipher.getAlphabet(); + + int[] textFreq = calculateCharacterFrequency(text, alphabet); + int[] sampleFreq = calculateCharacterFrequency(sample, alphabet); + + double similarity = 0; + int total = 0; + + for (int i = 0; i < textFreq.length; i++) { + if (sampleFreq[i] > 0) { + similarity += Math.min(textFreq[i], sampleFreq[i]); + total += sampleFreq[i]; + } + } + + return total > 0 ? similarity / total : 0; + } + + private int[] calculateCharacterFrequency(String text, char[] alphabet) { + int[] frequency = new int[alphabet.length]; + for (char c : text.toLowerCase().toCharArray()) { + for (int i = 0; i < alphabet.length; i++) { + if (c == alphabet[i]) { + frequency[i]++; + break; + } + } + } + return frequency; + } +} diff --git a/src/main/java/com/javarush/timoshenko/core/CaesarCipher.java b/src/main/java/com/javarush/timoshenko/core/CaesarCipher.java new file mode 100644 index 0000000..1d60e68 --- /dev/null +++ b/src/main/java/com/javarush/timoshenko/core/CaesarCipher.java @@ -0,0 +1,66 @@ +package com.javarush.timoshenko.core; + +import java.util.HashMap; +import java.util.Map; + +public class CaesarCipher { + public static final char[] RUSSIAN_ALPHABET = { + 'а', 'б', 'в', 'г', 'д', 'е', 'ж', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', + 'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'э', + 'ю', 'я', '.', ',', '«', '»', '"', '\'', ':', '!', '?', ' ' + }; + + public static final char[] ENGLISH_ALPHABET = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '.', ',', '"', '\'', + ':', '!', '?', ' ' + }; + + private final Map charToIndex; + private final char[] alphabet; + + public CaesarCipher(char[] alphabet) { + this.alphabet = alphabet; + this.charToIndex = new HashMap<>(); + for (int i = 0; i < alphabet.length; i++) { + charToIndex.put(alphabet[i], i); + } + } + + public String encrypt(String text, int shift) { + return processText(text, shift); + } + + public String decrypt(String encryptedText, int shift) { + return processText(encryptedText, alphabet.length - (shift % alphabet.length)); + } + + private String processText(String text, int shift) { + StringBuilder result = new StringBuilder(); + + for (char character : text.toCharArray()) { + char lowerChar = Character.toLowerCase(character); + Integer index = charToIndex.get(lowerChar); + + if (index != null) { + int newIndex = (index + shift) % alphabet.length; + char encryptedChar = alphabet[newIndex]; + + + if (Character.isUpperCase(character)) { + encryptedChar = Character.toUpperCase(encryptedChar); + } + + result.append(encryptedChar); + } else { + result.append(character); + } + } + + return result.toString(); + } + + public char[] getAlphabet() { + return alphabet; + } +} diff --git a/src/main/java/com/javarush/timoshenko/core/FileManager.java b/src/main/java/com/javarush/timoshenko/core/FileManager.java new file mode 100644 index 0000000..1cd8d48 --- /dev/null +++ b/src/main/java/com/javarush/timoshenko/core/FileManager.java @@ -0,0 +1,26 @@ +package com.javarush.timoshenko.core; + +import com.javarush.timoshenko.exception.CipherException; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class FileManager { + + public String readFile(String filePath) throws CipherException { + try { + return new String(Files.readAllBytes(Paths.get(filePath))); + } catch (IOException e) { + throw new CipherException("Ошибка чтения файла: " + filePath, e); + } + } + + public void writeFile(String content, String filePath) throws CipherException { + try { + Files.write(Paths.get(filePath), content.getBytes()); + } catch (IOException e) { + throw new CipherException("Ошибка записи в файл: " + filePath, e); + } + } +} diff --git a/src/main/java/com/javarush/timoshenko/core/StatisticalAnalyzer.java b/src/main/java/com/javarush/timoshenko/core/StatisticalAnalyzer.java new file mode 100644 index 0000000..375e254 --- /dev/null +++ b/src/main/java/com/javarush/timoshenko/core/StatisticalAnalyzer.java @@ -0,0 +1,66 @@ +package com.javarush.timoshenko.core; + +public class StatisticalAnalyzer { + private final CaesarCipher cipher; + + public StatisticalAnalyzer(CaesarCipher cipher) { + this.cipher = cipher; + } + + public int findMostLikelyShift(String encryptedText, String representativeText) { + int bestShift = 0; + double bestScore = 0; + char[] alphabet = cipher.getAlphabet(); + + + int[] refFrequencies = calculateCharacterFrequency(representativeText, alphabet); + + for (int shift = 0; shift < alphabet.length; shift++) { + String decrypted = cipher.decrypt(encryptedText, shift); + int[] decryptedFrequencies = calculateCharacterFrequency(decrypted, alphabet); + + double score = calculateFrequencyScore(decryptedFrequencies, refFrequencies); + + if (score > bestScore) { + bestScore = score; + bestShift = shift; + } + } + + return bestShift; + } + + private int[] calculateCharacterFrequency(String text, char[] alphabet) { + int[] frequency = new int[alphabet.length]; + int total = 0; + + for (char c : text.toLowerCase().toCharArray()) { + for (int i = 0; i < alphabet.length; i++) { + if (c == alphabet[i]) { + frequency[i]++; + total++; + break; + } + } + } + + + if (total > 0) { + for (int i = 0; i < frequency.length; i++) { + frequency[i] = (frequency[i] * 1000) / total; + } + } + + return frequency; + } + + private double calculateFrequencyScore(int[] freq1, int[] freq2) { + double score = 0; + for (int i = 0; i < freq1.length; i++) { + if (freq2[i] > 0) { + score += Math.min(freq1[i], freq2[i]); + } + } + return score; + } +} diff --git a/src/main/java/com/javarush/timoshenko/core/Validator.java b/src/main/java/com/javarush/timoshenko/core/Validator.java new file mode 100644 index 0000000..632ca26 --- /dev/null +++ b/src/main/java/com/javarush/timoshenko/core/Validator.java @@ -0,0 +1,33 @@ +package com.javarush.timoshenko.core; + +import java.io.File; + +public class Validator { + private final char[] alphabet; + + public Validator(char[] alphabet) { + this.alphabet = alphabet; + } + + public boolean isValidKey(int key) { + return key >= 0 && key < alphabet.length; + } + + public boolean isFileExists(String filePath) { + return new File(filePath).exists(); + } + + public boolean isValidOutputPath(String filePath) { + try { + java.nio.file.Path path = java.nio.file.Paths.get(filePath); + java.nio.file.Path parent = path.getParent(); + return parent == null || java.nio.file.Files.exists(parent) || java.nio.file.Files.isWritable(parent); + } catch (Exception e) { + return false; + } + } + + public char[] getAlphabet() { + return alphabet; + } +} diff --git a/src/main/java/com/javarush/timoshenko/exception/CipherException.java b/src/main/java/com/javarush/timoshenko/exception/CipherException.java new file mode 100644 index 0000000..f4e2816 --- /dev/null +++ b/src/main/java/com/javarush/timoshenko/exception/CipherException.java @@ -0,0 +1,17 @@ +package com.javarush.timoshenko.exception; + + +public class CipherException extends Exception { + + public CipherException(String message) { + super(message); + } + + public CipherException(String message, Throwable cause) { + super(message, cause); + } + + public CipherException(Throwable cause) { + super(cause); + } +}