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
8 changes: 8 additions & 0 deletions .idea/artifacts/CaesarCipher_jar.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

124 changes: 124 additions & 0 deletions .idea/uiDesigner.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Проєкт реалізує криптоаналізатор для класичного шифру Цезаря (без кодування спецсимволів), що дозволяє шифрувати та розшифровувати тексти англійською та українською мовами з вказаним ключем, а також підбирати ключ до зашифрованих файлів аналізуючи частоту символів у варіантах з різними ключами. Успішно реалізовано шифрування, розшифрування та підбір ключа з автоматичним визначенням мови зашифрованого тексту (українська/англійська) та використання відповідного словника за замовчуванням, у разі відсутнього аргументу шляху до файлу для текстового аналізу. Також було розроблено клас CLI для роботи з програмою, якщо вона запущена без аргументів. Не вдалось розібратись як зібрати jar, вказавши опцію для кодування, щоб при роботі з програмою через командний рядок не вказувати щоразу опцію -Dfile.encoding=UTF-8 для коректного відображення кирилиці та правильного сприйняття програмою шляхів до файлів з кириличними символами, якщо вони передаються в режимі роботи через CLI. При перевірці хотілось би, щоб ви звернули увагу на обробку та прокидування вийнятків, адже суттєво кращого рішення не вдалось придумати, та загальний підхід до реалізації проєкту.
114 changes: 114 additions & 0 deletions src/BruteForcer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import java.util.HashMap;
import java.util.Map;

public class BruteForcer {
private final Map<Character, Double> letterFrequencies;
private String mostLikelyText;
private int mostLikelyKey;

public BruteForcer(String staticAnalysisText, String encryptedText) {
if (staticAnalysisText != null) {
this.letterFrequencies = calculateLetterFrequency(staticAnalysisText);
} else {
if (isUkrainianText(encryptedText)) {
this.letterFrequencies = createUkrainianFrequencyMap();
} else {
this.letterFrequencies = createEnglishFrequencyMap();
}
}
}

private static Map<Character, Double> createEnglishFrequencyMap() {
Map<Character, Double> map = new HashMap<>();
char[] letters = {'e', 'a', 'r', 'i', 'o', 't', 'n', 's', 'l', 'c', 'u', 'd', 'p', 'm', 'h', 'g', 'b', 'f',
'y', 'w', 'k', 'v', 'x', 'z', 'j', 'q'};
double[] frequencies = {11.1607, 8.4966, 7.5809, 7.5448, 7.1635, 6.9509, 6.6544, 5.7351, 5.4893, 4.5388,
3.6308, 3.3844, 3.1671, 3.0129, 3.0034, 2.4705, 2.0720, 1.8121, 1.7779, 1.2899, 1.1016, 1.0074, 0.2902,
0.2722, 0.1965, 0.1962};
for (int i = 0; i < letters.length; i++) {
map.put(letters[i], frequencies[i]);
}
return map;
}

private static Map<Character, Double> createUkrainianFrequencyMap() {
Map<Character, Double> map = new HashMap<>();
char[] letters = {'о', 'а', 'н', 'і', 'и', 'в', 'р', 'т', 'е', 'с', 'к', 'л', 'у', 'д', 'м', 'п', 'я', 'з',
'ь', 'г', 'б', 'й', 'х', 'ч', 'ц', 'ї', 'ж', 'ш', 'ю', 'є', 'ф', 'щ', 'ґ'};
double[] frequencies = {9.28, 8.34, 7.10, 6.23, 6.00, 5.50, 5.48, 4.77, 4.59, 4.57, 4.00, 3.93, 3.38, 3.06,
3.02, 2.84, 2.16, 2.10, 1.83, 1.59, 1.53, 1.24, 1.17, 1.15, 1.02, 0.84, 0.71, 0.71, 0.70, 0.39, 0.35,
0.32, 0.01};
for (int i = 0; i < letters.length; i++) {
map.put(letters[i], frequencies[i]);
}
return map;
}

private static boolean isUkrainianText(String text) {
for (char c : text.toCharArray()) {
if (CaesarCipher.getUkrainianAlphabet().indexOf(c) != -1) {
return true;
}
}
return false;
}

public void bruteForce(String encryptedText) {
int likelyKey = 0;
String likelyText = encryptedText;
double bestMatchScore = Double.MAX_VALUE;

for (int key = 0; key < letterFrequencies.size(); key++) {
String decryptedText = CaesarCipher.decrypt(encryptedText, key);
double score = calculateMatchScore(decryptedText);

if (score < bestMatchScore) {
bestMatchScore = score;
likelyKey = key;
likelyText = decryptedText;
}
}
this.mostLikelyKey = likelyKey;
this.mostLikelyText = likelyText;
}

private Map<Character, Double> calculateLetterFrequency(String text) {
Map<Character, Integer> counts = new HashMap<>();
int totalLetters = 0;

for (char c : text.toLowerCase().toCharArray()) {
if (Character.isLetter(c)) {
counts.put(c, counts.getOrDefault(c, 0) + 1);
totalLetters++;
}
}

Map<Character, Double> frequencies = new HashMap<>();
for (Map.Entry<Character, Integer> entry : counts.entrySet()) {
frequencies.put(entry.getKey(), (entry.getValue() * 100.0) / totalLetters);
}
return frequencies;
}

private double calculateMatchScore(String text) {
Map<Character, Double> textFrequency = calculateLetterFrequency(text);
double score = 0.0;

for (Map.Entry<Character, Double> entry : this.letterFrequencies.entrySet()) {
char letter = entry.getKey();
double expectedFrequency = entry.getValue();
double actualFrequency = textFrequency.getOrDefault(letter, 0.0);

score += Math.pow(actualFrequency - expectedFrequency, 2);
}
return score;
}

public String getMostLikelyText() {
return mostLikelyText;
}

public int getMostLikelyKey() {
return mostLikelyKey;
}
}
85 changes: 84 additions & 1 deletion src/CLI.java
Original file line number Diff line number Diff line change
@@ -1,2 +1,85 @@
import java.nio.file.Path;
import java.util.Scanner;

public class CLI {
}
private final Scanner scanner;
private final Runner runner;

public CLI() {
this.scanner = new Scanner(System.in);
this.runner = new Runner();
}

public void start() {
Command command = promptCommand();
Path filePath = promptFilePath();
int key = 0;
Path filePathForStaticAnalysis = null;

if (command == Command.BRUTE_FORCE) {
filePathForStaticAnalysis = promptOptionalFilePathForStaticAnalysis();
} else {
key = promptKey(command);
}
runner.run(command,filePath,key,filePathForStaticAnalysis);
}

private Command promptCommand() {
Command command = null;
while (command == null) {
System.out.println("Введіть команду (ENCRYPT, DECRYPT, або BRUTE_FORCE):");
String input = scanner.nextLine().trim();

try {
command = runner.checkCommand(input);
} catch (RuntimeException e) {
System.err.println(e.getMessage());
}
}
return command;
}

private Path promptFilePath() {
Path filePath = null;
while (filePath == null) {
System.out.println("Введіть повний шлях до файлу:");
String pathInput = scanner.nextLine();

try {
filePath = runner.checkFile(pathInput);
} catch (RuntimeException e) {
System.err.println(e.getMessage());
}
}
return filePath;
}

private Integer promptKey(Command command) {
Integer key = null;
while (key == null) {
System.out.println("Введіть ключ (ціле число):");
String keyInput = scanner.nextLine().trim();

try {
key = runner.checkKey(new String[]{"", "", keyInput}, command);
} catch (RuntimeException e) {
System.err.println(e.getMessage());
}
}
return key;
}

private Path promptOptionalFilePathForStaticAnalysis() {
System.out.println("Введіть повний шлях до файлу для частотного аналізу (або залиште порожнім, щоб пропустити):");
String pathInput = scanner.nextLine().trim();

if (!pathInput.isEmpty()) {
try {
return runner.checkFile(pathInput);
} catch (RuntimeException e) {
System.err.println(e.getMessage());
}
}
return null;
}
}
Loading