-
Notifications
You must be signed in to change notification settings - Fork 47
Timoshenko. 4 tasks complete. Rady CryptoAnaliz! #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. самые важные методы идут самыми первыми, а второстепенные опускаются в конец класса, так проще понимать логику его работы |
||
| new MainApp().run(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Дизайн этого метода неудачный, практически всегда если число строк в методе превышает один скролл, а это примерно 25 строк, его разбивают на подметоды каждому из которых дают осмысленное названия |
||
| 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, | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Данный массив выглядит как магия, посторонний человек никогда не догадается сразу что означает каждое из этих цифр, если эти частоты относятся к определённым буквам то лучше бы было сделать карту, тогда все было бы понятно, а размер этой карты как раз указал бы количество букв |
||
| 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[]{ | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. тут ещё одна странность, при каждом обращении к методу оба этих массива будут всякий раз рождаться заново, и всякий раз с одними и теми же значениями, намного лучше было бы вынести их в статическое поле и там проинициализировать. Это важно для сборки мусора |
||
| 0.0812, 0.0149, 0.0271, 0.0432, 0.1202, 0.0230, 0.0203, 0.0592, 0.0731, | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. тут тоже самое |
||
| 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; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
данный класс выполняет сразу две функции с одной стороны он собирает все компоненты приложения вместе, а с другой стороны отвечает пользовательский ввод, реализацию вызова расчётной функциональности, и вывод результатов. Очевидно что его нужно разбить как минимум на несколько классов, по одному на каждое направление ответственности