diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000..e523bee --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: ua.com.javarush.gnew.Main + diff --git a/out/artifacts/GNEW_M1_FP_jar/GNEW-M1-FP.jar b/out/artifacts/GNEW_M1_FP_jar/GNEW-M1-FP.jar new file mode 100644 index 0000000..d75d678 Binary files /dev/null and b/out/artifacts/GNEW_M1_FP_jar/GNEW-M1-FP.jar differ diff --git a/readme.md b/readme.md index a17a9ac..46c698f 100644 --- a/readme.md +++ b/readme.md @@ -22,7 +22,32 @@ -bf -f "/path/to/file [ENCRYPTED].txt" - Brute force decrypt file ``` -### Argument could be in any order +### Arguments could be in any order ``` -e -f "/path/to/file.txt" -k 1 -``` \ No newline at end of file +``` + +## About the project + +### Implemented tasks +All mandatory tasks are completed. +- Program compiled in jar format and released on GitHub. +- Program can be run from the console by passing arguments. +- Program implements three options: [ENCRYPT, DECRYPT, BRUTE_FORCE]. +- Program implements only one option at a time. +- If you use Encrypt or Decrypt command, key value is mandatory. +- Program writes the result into a new file, which name contains +a label according to command used: [ENCRYPTED], [DECRYPTED], +[BRUTE_FORCE]. In case of using Bruteforce, the calculated key value +is appended to the file name as well. +- Program can be used English language only. +- Only letters of latin alphabet are encrypted. +- After decryption, the text has exactly the same formatting as the original file +- (spaces, indents, line separators, symbols, upper and lower case letters). + +### Not implemented tasks +- Program does not work for Ukrainian or any other languages. +- Program does not encode any special or blank symbols (latin letters only). +- Program does not work with CLI. +- Program does not have UI. + diff --git a/src/main/java/ua/com/javarush/gnew/Main.java b/src/main/java/ua/com/javarush/gnew/Main.java index 5906828..b108266 100644 --- a/src/main/java/ua/com/javarush/gnew/Main.java +++ b/src/main/java/ua/com/javarush/gnew/Main.java @@ -1,12 +1,13 @@ package ua.com.javarush.gnew; -import ua.com.javarush.gnew.crypto.Cypher; -import ua.com.javarush.gnew.file.FileManager; +import ua.com.javarush.gnew.cypher.Cypher; +import ua.com.javarush.gnew.fileManager.FileManager; import ua.com.javarush.gnew.runner.ArgumentsParser; -import ua.com.javarush.gnew.runner.Command; import ua.com.javarush.gnew.runner.RunOptions; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.*; public class Main { public static void main(String[] args) { @@ -15,16 +16,39 @@ public static void main(String[] args) { ArgumentsParser argumentsParser = new ArgumentsParser(); RunOptions runOptions = argumentsParser.parse(args); + List linesRead; + List linesWritten = new ArrayList<>(); + Path resultFile = null; + try { - if (runOptions.getCommand() == Command.ENCRYPT) { - String content = fileManager.read(runOptions.getFilePath()); - String encryptedContent = cypher.encrypt(content, runOptions.getKey()); - String fileName = runOptions.getFilePath().getFileName().toString(); - String newFileName = fileName.substring(0, fileName.length() - 4) + " [ENCRYPTED].txt"; + linesRead = fileManager.read(runOptions.getFilePath()); + switch (runOptions.getCommand()) { + case ENCRYPT: + for (String lineRead : linesRead) { + linesWritten.add(cypher.encrypt(lineRead, runOptions.getKey())); + } + resultFile = fileManager.getNewPath(runOptions.getFilePath(), runOptions.getCommand()); + break; + case DECRYPT: + for (String lineRead : linesRead) { + linesWritten.add(cypher.decrypt(lineRead, runOptions.getKey())); + } + resultFile = fileManager.getNewPath(runOptions.getFilePath(), runOptions.getCommand()); + break; + case BRUTEFORCE: + int key = cypher.bruteForceKey(linesRead); + for (String lineRead : linesRead) { + linesWritten.add(cypher.decrypt(lineRead, key)); + } + resultFile = fileManager.getNewPath(runOptions.getFilePath(), key); + break; + } - Path newFilePath = runOptions.getFilePath().resolveSibling(newFileName); - fileManager.write(newFilePath, encryptedContent); + if (Files.notExists(resultFile)) { + Files.createFile(resultFile); } + fileManager.write(resultFile, linesWritten); + } catch (Exception e) { System.out.println(e.getMessage()); } diff --git a/src/main/java/ua/com/javarush/gnew/crypto/Cypher.java b/src/main/java/ua/com/javarush/gnew/crypto/Cypher.java deleted file mode 100644 index 2b01247..0000000 --- a/src/main/java/ua/com/javarush/gnew/crypto/Cypher.java +++ /dev/null @@ -1,33 +0,0 @@ -package ua.com.javarush.gnew.crypto; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; - -public class Cypher { - private final ArrayList originalAlphabet = new ArrayList<>(Arrays.asList('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', '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')); - - - public String encrypt(String input, int key) { - key = Math.negateExact(key); - - ArrayList rotatedAlphabet = new ArrayList<>(originalAlphabet); - Collections.rotate(rotatedAlphabet, key); - char[] charArray = input.toCharArray(); - - StringBuilder builder = new StringBuilder(); - for (char symbol : charArray) { - builder.append(processSymbol(symbol, rotatedAlphabet)); - } - return builder.toString(); - } - - private Character processSymbol(char symbol, ArrayList rotatedAlphabet) { - if (!originalAlphabet.contains(symbol)) { - return symbol; - } - int index = originalAlphabet.indexOf(symbol); - - return rotatedAlphabet.get(index); - } -} diff --git a/src/main/java/ua/com/javarush/gnew/cypher/Cypher.java b/src/main/java/ua/com/javarush/gnew/cypher/Cypher.java new file mode 100644 index 0000000..bab411c --- /dev/null +++ b/src/main/java/ua/com/javarush/gnew/cypher/Cypher.java @@ -0,0 +1,100 @@ +package ua.com.javarush.gnew.cypher; + +import ua.com.javarush.gnew.language.Alphabet; + +import java.util.ArrayList; +import java.util.List; + +public class Cypher { + + public String encrypt(String text, int key) { + + ArrayList rotatedUppercase = Alphabet.rotateAlphabet(Alphabet.ENGLISH_UPPERCASE, key); + ArrayList rotatedLowercase = Alphabet.rotateAlphabet(Alphabet.ENGLISH_LOWERCASE, key); + StringBuilder result = new StringBuilder(); + + for (int i = 0; i < text.length(); i++) { + char oneChar = text.charAt(i); // беремо символ з тексту + int index = Alphabet.ENGLISH_LOWERCASE.indexOf(oneChar); // шукаємо його в алфавіті lowercase + if (index != -1) { // якщо знайшли + result.append(rotatedLowercase.get(index)); // шифруємо і додаємо до результату + } else { // якщо немає + index = Alphabet.ENGLISH_UPPERCASE.indexOf(oneChar); // шукаємо його в алфавіті uppercase + if (index != -1) { // якщо знайшли + result.append(rotatedUppercase.get(index)); // шифруємо і додаємо до результату + } else { // якщо не знайшли в жодному з двох алфавітів, це спеціальний символ + result.append(text.charAt(i)); // НЕ шифруємо і просто додаємо + } + } + } + return result.toString(); + } + + public String decrypt(String text, int key) { + // decrypt робить те саме, що й encrypt, тільки з протилежним значенням ключа + return encrypt(text, -key); + } + + public int bruteForceKey(List text) { + String encrypted = ""; + String decrypted = ""; + + for (String line : text) { + encrypted = line; + for (int i = 0; i < 26; i++) { + decrypted = decrypt(line, i); + if (checkForCommonWords(decrypted)) { + break; + } + } + break; + } + + return calculateKey(encrypted, decrypted); + } + + private int calculateKey(String encrypted, String decrypted) { + int firstLetterIndex = 0; + for (int i = 0; i < decrypted.length(); i++) { + if (Character.isLetter(decrypted.charAt(i))) { + firstLetterIndex = i; + break; + } + } + + int indexEncrypted = Alphabet.ENGLISH_LOWERCASE.indexOf(Character.toLowerCase(encrypted.charAt(firstLetterIndex))); + int indexDecrypted = Alphabet.ENGLISH_LOWERCASE.indexOf(Character.toLowerCase(decrypted.charAt(firstLetterIndex))); + + if (indexEncrypted < indexDecrypted) { + indexEncrypted = indexEncrypted + 26; + } + return indexEncrypted - indexDecrypted; + } + + private boolean checkForCommonWords(String text) { + ArrayList words = getWords(text); + boolean containsCommonWords = false; + + for (String word : words) { + if (Alphabet.ENGLISH_COMMON_WORDS.contains(word.toLowerCase())) { + containsCommonWords = true; + break; + } + } + + return containsCommonWords; + } + + private ArrayList getWords(String text) { + String[] words = text.replaceAll("[^a-zA-Z ]", "").split("\\s+"); + + ArrayList wordList = new ArrayList<>(); + for (String word : words) { + if (!word.isEmpty()) { + wordList.add(word); + } + } + + return wordList; + } +} \ No newline at end of file diff --git a/src/main/java/ua/com/javarush/gnew/file/FileManager.java b/src/main/java/ua/com/javarush/gnew/file/FileManager.java deleted file mode 100644 index d60744c..0000000 --- a/src/main/java/ua/com/javarush/gnew/file/FileManager.java +++ /dev/null @@ -1,15 +0,0 @@ -package ua.com.javarush.gnew.file; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -public class FileManager { - public String read(Path filePath) throws IOException { - return Files.readString(filePath); - } - - public void write(Path filePath, String content) throws IOException { - Files.writeString(filePath, content); - } -} diff --git a/src/main/java/ua/com/javarush/gnew/fileManager/FileManager.java b/src/main/java/ua/com/javarush/gnew/fileManager/FileManager.java new file mode 100644 index 0000000..ab0f68d --- /dev/null +++ b/src/main/java/ua/com/javarush/gnew/fileManager/FileManager.java @@ -0,0 +1,41 @@ +package ua.com.javarush.gnew.fileManager; + +import ua.com.javarush.gnew.runner.Command; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +public class FileManager { + + public List read(Path path) throws IOException { + return Files.readAllLines(path); + } + + public void write(Path path, List lines) throws IOException { + String content = String.join("\n", lines); + Files.writeString(path, content); + } + + public Path getNewPath(Path path, Command command) { + String markerOfAction = switch (command) { + case DECRYPT: + yield " [DECRYPTED].txt"; + case ENCRYPT: + yield " [ENCRYPTED].txt"; + default: + yield ".txt"; + }; + + String originalPath = path.toString(); + String newPath = originalPath.substring(0, originalPath.length() - 4) + markerOfAction; + return Path.of(newPath); + } + + public Path getNewPath(Path path, int key) { + String originalPath = path.toString(); + String newPath = originalPath.substring(0, originalPath.length() - 4) + " [BRUTE_FORCE] " + key + ".txt"; + return Path.of(newPath); + } +} \ No newline at end of file diff --git a/src/main/java/ua/com/javarush/gnew/language/Alphabet.java b/src/main/java/ua/com/javarush/gnew/language/Alphabet.java new file mode 100644 index 0000000..b2d001a --- /dev/null +++ b/src/main/java/ua/com/javarush/gnew/language/Alphabet.java @@ -0,0 +1,31 @@ +package ua.com.javarush.gnew.language; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +public abstract class Alphabet { + + public static final ArrayList ENGLISH_UPPERCASE = new ArrayList<>(Arrays.asList( + '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')); + public static final ArrayList ENGLISH_LOWERCASE = new ArrayList<>(Arrays.asList( + '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')); + public static final ArrayList ENGLISH_COMMON_WORDS = new ArrayList<>(Arrays.asList( + "the", "be", "to", "of", "and", "in", "that", "have", "for", "not", + "with", "he", "as", "you", "do", "this", "but", "his", "by", "from", + "they", "we", "say", "her", "she", "or", "will", "my", "one", "all", + "would", "there", "their", "what", "so", "up", "out", "if", "about", + "who", "get", "which", "go", "me", "it", "can", "has", "had", "are", "hello" + )); + + public static ArrayList rotateAlphabet(ArrayList originalAlphabet, int key) { + + ArrayList rotatedAlphabet = new ArrayList<>(originalAlphabet); + + Collections.rotate(rotatedAlphabet, -key); + + return rotatedAlphabet; + } +} \ No newline at end of file diff --git a/src/main/java/ua/com/javarush/gnew/language/Language.java b/src/main/java/ua/com/javarush/gnew/language/Language.java deleted file mode 100644 index 067dd2f..0000000 --- a/src/main/java/ua/com/javarush/gnew/language/Language.java +++ /dev/null @@ -1,12 +0,0 @@ -package ua.com.javarush.gnew.language; - -import java.util.ArrayList; - -public abstract class Language { - - private final ArrayList alphabet; - - public Language(ArrayList alphabet) { - this.alphabet = alphabet; - } -} diff --git a/src/main/java/ua/com/javarush/gnew/runner/ArgumentsParser.java b/src/main/java/ua/com/javarush/gnew/runner/ArgumentsParser.java index eb42462..fab53d4 100644 --- a/src/main/java/ua/com/javarush/gnew/runner/ArgumentsParser.java +++ b/src/main/java/ua/com/javarush/gnew/runner/ArgumentsParser.java @@ -22,6 +22,7 @@ public RunOptions parse(String[] args) { case "-bf": command = Command.BRUTEFORCE; + key = 0; break; case "-k": if (i + 1 < args.length) { @@ -58,5 +59,4 @@ public RunOptions parse(String[] args) { return new RunOptions(command, key, filePath); } - -} +} \ No newline at end of file diff --git a/src/main/java/ua/com/javarush/gnew/runner/RunOptions.java b/src/main/java/ua/com/javarush/gnew/runner/RunOptions.java index 45e700b..1a730b3 100644 --- a/src/main/java/ua/com/javarush/gnew/runner/RunOptions.java +++ b/src/main/java/ua/com/javarush/gnew/runner/RunOptions.java @@ -51,4 +51,4 @@ public String toString() { ", filePath=" + filePath + '}'; } -} +} \ No newline at end of file