Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 219 additions & 0 deletions src/main/java/com/javarush/timoshenko/MainApp.java
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 {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

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) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

new MainApp().run();
}
}
119 changes: 119 additions & 0 deletions src/main/java/com/javarush/timoshenko/core/BruteForce.java
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) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The 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,
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The 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[]{
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The 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,
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The 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;
}
}
Loading