diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..692d80e --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,95 @@ +name: Java CI with JUnit Tests + +on: + push: + branches: [ "main", "develop" ] + pull_request: + branches: [ "main", "develop" ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 23 + uses: actions/setup-java@v3 + with: + java-version: '23' + distribution: 'temurin' + + - name: Show Project Structure + run: | + echo "Current directory:" + pwd + echo "Directory contents:" + ls -R + + - name: Download Dependencies + run: | + mkdir -p lib + wget https://repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2.jar -P lib/ + wget https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar -P lib/ + wget https://repo1.maven.org/maven2/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar -P lib/ + wget https://repo1.maven.org/maven2/org/bytedeco/javacv/1.5.11/javacv-1.5.11.jar -P lib/ + wget https://repo1.maven.org/maven2/org/bytedeco/javacpp/1.5.11/javacpp-1.5.11.jar -P lib/ + wget https://repo1.maven.org/maven2/org/bytedeco/ffmpeg/7.1-1.5.11/ffmpeg-7.1-1.5.11.jar -P lib/ + wget https://repo1.maven.org/maven2/org/bytedeco/ffmpeg/7.1-1.5.11/ffmpeg-7.1-1.5.11-linux-x86_64.jar -P lib/ + wget https://repo1.maven.org/maven2/org/bytedeco/javacpp-platform/1.5.11/javacpp-platform-1.5.11.jar -P lib/ + + + - name: Compile + run: | + mkdir -p out + # Liste et compile les fichiers principaux + MAIN_FILES=$(find src/Classes -name "*.java") + if [ ! -z "$MAIN_FILES" ]; then + echo "Compiling main files:" + echo "$MAIN_FILES" + javac -cp "lib/*" -d out $MAIN_FILES + else + echo "No main files found" + fi + + # Liste et compile les fichiers de test + TEST_FILES=$(find src/Tests -name "*.java") + if [ ! -z "$TEST_FILES" ]; then + echo "Compiling test files:" + echo "$TEST_FILES" + javac -cp "out:lib/*" -d out $TEST_FILES + else + echo "No test files found" + fi + + - name: Run tests + run: | + # Trouve et exécute tous les tests + TEST_CLASSES=$(find out/Tests -name "*Test.class" | sed 's/out\///' | sed 's/\.class$//' | tr '/' '.') + if [ ! -z "$TEST_CLASSES" ]; then + echo "Running test classes:" + echo "$TEST_CLASSES" + java -cp "out:lib/*" org.junit.runner.JUnitCore $TEST_CLASSES + else + echo "No test classes found" + fi + + - name: Generate Test Report + if: always() + run: | + echo "# Test Results" > test-report.md + echo "\`\`\`" >> test-report.md + TEST_CLASSES=$(find out/Tests -name "*Test.class" | sed 's/out\///' | sed 's/\.class$//' | tr '/' '.') + if [ ! -z "$TEST_CLASSES" ]; then + java -cp "out:lib/*" org.junit.runner.JUnitCore $TEST_CLASSES >> test-report.md 2>&1 || true + else + echo "No test classes found" >> test-report.md + fi + echo "\`\`\`" >> test-report.md + + - name: Upload Test Report + if: always() + uses: actions/upload-artifact@v3 + with: + name: test-report + path: test-report.md \ No newline at end of file diff --git a/.idea/artifacts/ByteWarden_jar.xml b/.idea/artifacts/ByteWarden_jar.xml new file mode 100644 index 0000000..2dc119f --- /dev/null +++ b/.idea/artifacts/ByteWarden_jar.xml @@ -0,0 +1,152 @@ + + + $PROJECT_DIR$/out/artifacts/ByteWarden + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/bytedeco_javacv_platform.xml b/.idea/libraries/bytedeco_javacv_platform.xml new file mode 100644 index 0000000..9fa3834 --- /dev/null +++ b/.idea/libraries/bytedeco_javacv_platform.xml @@ -0,0 +1,284 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/fasterxml_jackson_core_databind.xml b/.idea/libraries/fasterxml_jackson_core_databind.xml new file mode 100644 index 0000000..142f4e6 --- /dev/null +++ b/.idea/libraries/fasterxml_jackson_core_databind.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/google_code_gson.xml b/.idea/libraries/google_code_gson.xml new file mode 100644 index 0000000..f62abb1 --- /dev/null +++ b/.idea/libraries/google_code_gson.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 31e1ebc..104fe35 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/ByteWarden.iml b/ByteWarden.iml index c3dc060..27a65dc 100644 --- a/ByteWarden.iml +++ b/ByteWarden.iml @@ -17,5 +17,8 @@ + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ef3791f --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +![Tests](https://github.com/Bilnaa/ByteWarden/actions/workflows/tests.yml/badge.svg) \ No newline at end of file diff --git a/databases.json b/databases.json new file mode 100644 index 0000000..4cb7c65 --- /dev/null +++ b/databases.json @@ -0,0 +1,18 @@ +{ + "llucas": { + "name": "llucas", + "hashPassword": "eebc1115b82aea1ee598f927e19500c75d2e318eaa9d83fa5a7513e26dea608b", + "encryptionMap": { + "RotX": "12" + }, + "salt": "NTNcG1Mv\u002604K" + }, + "test": { + "name": "test", + "hashPassword": "6b3fcb519f1dac8495538eed6bf3963faf7d7a6762d6789869a2cb51a9bca91d", + "encryptionMap": { + "RotX": "12" + }, + "salt": "sP5s3cWwMsOX" + } +} \ No newline at end of file diff --git a/llucas.json b/llucas.json new file mode 100644 index 0000000..e104e2e --- /dev/null +++ b/llucas.json @@ -0,0 +1,9 @@ +{ + "eufqe": [ + { + "bmeeiadp": "rlrqlr", + "eufqZmyq": "kagfgnq", + "geqdzmyq": "lplp" + } + ] +} \ No newline at end of file diff --git a/pepper.json b/pepper.json new file mode 100644 index 0000000..51db507 --- /dev/null +++ b/pepper.json @@ -0,0 +1,3 @@ +{ + "pepper": "jesuisunpoivre" +} \ No newline at end of file diff --git a/src/Classes/DatabasesManager.java b/src/Classes/DatabasesManager.java new file mode 100644 index 0000000..85d6d70 --- /dev/null +++ b/src/Classes/DatabasesManager.java @@ -0,0 +1,124 @@ +package Classes; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +public class DatabasesManager { + private final File databasesFile; + private final Map databases; + private final Sha256 sha256 = new Sha256(); + + public DatabasesManager(File databasesFile) { + this.databasesFile = databasesFile; + this.databases = loadDatabases() != null ? loadDatabases() : new HashMap<>(); + } + //Verify the hashed password with the password entered by the user + public boolean verifyDatabase(String dbName, String password) { + if (!databases.containsKey(dbName)) return false; + Database database = databases.get(dbName); + String pepper = readPepperFromJson("pepper.json"); + + String hashedPassword = sha256.calculateHash(database.getSalt() + password + pepper); + return database.getHashPassword().equals(hashedPassword); + } + //create database object with user inputs + public void createDatabase(String dbName, String password, Map encryptionMap) { + if (databases.containsKey(dbName)) { + throw new IllegalArgumentException("Database already exists."); + } + String pepper = readPepperFromJson("pepper.json"); + String salt = PasswordUtils.generateRandomPassword(12); + String hashedPassword = sha256.calculateHash(salt + password + pepper); + Database newDatabase = new Database(dbName, hashedPassword, encryptionMap, salt); + databases.put(dbName, newDatabase); + saveDatabases(); + } + + private String readPepperFromJson(String filePath) { + ObjectMapper objectMapper = new ObjectMapper(); + try { + Map jsonMap = objectMapper.readValue(new File(filePath), Map.class); + return jsonMap.get("pepper"); + } catch (IOException e) { + e.printStackTrace(); + return ""; + } + } + private Map loadDatabases() { + if (!databasesFile.exists()) return new HashMap<>(); + try (FileReader reader = new FileReader(databasesFile)) { + Gson gson = new Gson(); + Type type = new TypeToken>() {}.getType(); + return gson.fromJson(reader, type); + } catch (IOException e) { + e.printStackTrace(); + return new HashMap<>(); + } + } + + + private void saveDatabases() { + try (FileWriter writer = new FileWriter(databasesFile)) { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + gson.toJson(databases, writer); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // class database to make the database manager easy to use + public static class Database { + private final String name; + private final String hashPassword; + private final Map encryptionMap; + private final String salt; + public Database(String name, String hashPassword, Map encryptionMap, String salt) { + this.name = name; + this.hashPassword = hashPassword; + this.encryptionMap = encryptionMap; + this.salt = salt; + } + + public String getName() { + return name; + } + + public String getHashPassword() { + return hashPassword; + } + + public Map getEncryptionMap() { + return encryptionMap; + } + public String getSalt(){ + return salt; + } + + + } + //method to get the encryption map for the encryption and decryption during the database user selection + public Map getEncryptionMap(String dbName) { + if (!databases.containsKey(dbName)) { // if the database name doesn't exist + throw new IllegalArgumentException("Database does not exist: " + dbName); + } + try (FileReader reader = new FileReader(databasesFile)) { + Gson gson = new Gson(); + Map> allDatabases = gson.fromJson(reader, Map.class); //get all the databases info from databases.json + Map dbData = allDatabases.get(dbName); //get the wanted database + return (Map) dbData.get("encryptionMap"); // get all the encryption methods from the current database + } catch (IOException e) { + throw new RuntimeException("Failed to read databases file.", e); + } + } + +} diff --git a/src/Classes/Enigma/Enigma.java b/src/Classes/Enigma/Enigma.java new file mode 100644 index 0000000..39aa0fe --- /dev/null +++ b/src/Classes/Enigma/Enigma.java @@ -0,0 +1,52 @@ +package Classes.Enigma; + +public class Enigma { + private Rotors rotors = new Rotors(); // Rotors component + private Reflector reflector = new Reflector(); // Reflector component + private Plugboard plugboard = new Plugboard(); // Plugboard component + + public Enigma() { + } + + public Enigma(Rotors rotors, Reflector reflector, Plugboard plugboard) { + this.rotors = rotors; + this.reflector = reflector; + this.plugboard = plugboard; + } + + public Rotors getRotors() { + return rotors; // Return the rotors component + } + + public Reflector getReflector() { + return reflector; // Return the reflector component + } + + public Plugboard getPlugboard() { + return plugboard; // Return the plugboard component + } + + public String encrypt(String message) { + StringBuilder encryptedMessage = new StringBuilder(); + for (char c : message.toCharArray()) { + if (Character.isLetter(c)) { + c = Character.toUpperCase(c); // Convert to uppercase + c = plugboard.swap(c); // Swap using plugboard + c = rotors.rotate(c); // Rotate through rotors + c = reflector.reflect(c); // Reflect the character + c = rotors.rotateBack(c); // Rotate back through rotors + c = plugboard.swap(c); // Swap using plugboard again + if (Character.isLowerCase(message.charAt(encryptedMessage.length()))) { + c = Character.toLowerCase(c); // Convert back to lowercase if needed + } + } + encryptedMessage.append(c); // Append the encrypted character + } + return encryptedMessage.toString(); + } + + public String decrypt(String message) { + rotors.resetToInitialPosition(); // Reset rotors to initial positions + return encrypt(message); // Decrypt by re-encrypting + } +} \ No newline at end of file diff --git a/src/Classes/Enigma/Plugboard.java b/src/Classes/Enigma/Plugboard.java new file mode 100644 index 0000000..d867041 --- /dev/null +++ b/src/Classes/Enigma/Plugboard.java @@ -0,0 +1,40 @@ +package Classes.Enigma; + +import java.util.HashMap; +import java.util.Map; + +public class Plugboard { + private Map connections; // Map to store the letter pairs for swapping + + public Plugboard() { + connections = new HashMap<>(); // Initialize the connections map + } + + /** + * Connects two letters in the plugboard + * @param a First letter to connect + * @param b Second letter to connect + * @throws IllegalArgumentException if letters are already connected + */ + public void connect(char a, char b) { + a = Character.toUpperCase(a); // Convert to uppercase for consistency + b = Character.toUpperCase(b); + + if (connections.containsKey(a) || connections.containsKey(b)) { + throw new IllegalArgumentException("Letters are already connected"); // Check if letters are already connected + } + + connections.put(a, b); // Create bidirectional connection + connections.put(b, a); + } + + /** + * Swaps a letter according to plugboard connections + * @param c Letter to swap + * @return Swapped letter if connection exists, original letter otherwise + */ + public char swap(char c) { + c = Character.toUpperCase(c); // Convert to uppercase for consistency + return connections.getOrDefault(c, c); // Swap the letter if connection exists + } +} \ No newline at end of file diff --git a/src/Classes/Enigma/Reflector.java b/src/Classes/Enigma/Reflector.java new file mode 100644 index 0000000..83b5bf6 --- /dev/null +++ b/src/Classes/Enigma/Reflector.java @@ -0,0 +1,16 @@ +package Classes.Enigma; + +public class Reflector { + private final String reflector = "YRUHQSLDPXNGOKMIEBFZCWVJAT"; // Reflector wiring + + public Reflector() { + } + + public String getReflector() { + return reflector; // Return the reflector wiring + } + + public char reflect(char c) { + return reflector.charAt(c - 'A'); // Reflect the character + } +} diff --git a/src/Classes/Enigma/Rotors.java b/src/Classes/Enigma/Rotors.java new file mode 100644 index 0000000..3f476f9 --- /dev/null +++ b/src/Classes/Enigma/Rotors.java @@ -0,0 +1,99 @@ +package Classes.Enigma; + +public class Rotors { + private final Rotor[] rotors; // Array of Rotor objects + private char[] initialPositions; // Initial positions of the rotors + + private static class Rotor { + private final String wiring; // Wiring configuration of the rotor + private int position; // Current position of the rotor + private final int notchPosition; // Position of the notch + + public Rotor(String wiring, int notchPosition) { + this.wiring = wiring; + this.position = 0; + this.notchPosition = notchPosition; + } + + public void rotate() { + position = (position + 1) % 26; // Rotate the rotor by one position + } + + public boolean isAtNotch() { + return position == notchPosition; // Check if the rotor is at the notch position + } + + public char encrypt(char c) { + int inputPos = (Character.toUpperCase(c) - 'A' + position) % 26; // Calculate input position + char output = wiring.charAt(inputPos); // Get the output character from the wiring + return (char)((output - 'A' - position + 26) % 26 + 'A'); // Return the encrypted character + } + + public char encryptBack(char c) { + int inputPos = (Character.toUpperCase(c) - 'A' + position) % 26; // Calculate input position + int outputPos = wiring.indexOf((char)(inputPos + 'A')); // Get the output position from the wiring + return (char)((outputPos - position + 26) % 26 + 'A'); // Return the decrypted character + } + } + + public Rotors() { + rotors = new Rotor[3]; // Initialize the array of rotors + rotors[0] = new Rotor("EKMFLGDQVZNTOWYHXUSPAIBRCJ", 16); // Rotor I + rotors[1] = new Rotor("AJDKSIRUXBLHWTMCQGZNPYFVOE", 4); // Rotor II + rotors[2] = new Rotor("BDFHJLCPRTXVZNYEIWGAKMUSQO", 21); // Rotor III + } + + private void step() { + boolean middleAtNotch = rotors[1].isAtNotch(); // Check if the middle rotor is at the notch + + if (middleAtNotch) { + rotors[0].rotate(); // Rotate the left rotor + rotors[1].rotate(); // Rotate the middle rotor + } + + if (rotors[2].isAtNotch()) { + rotors[1].rotate(); // Rotate the middle rotor if the right rotor is at the notch + } + + rotors[2].rotate(); // Always rotate the right rotor + } + + public char rotate(char c) { + step(); // Step the rotors + char result = c; + for (int i = rotors.length - 1; i >= 0; i--) { + result = rotors[i].encrypt(result); // Encrypt the character through the rotors + } + return result; + } + + public char rotateBack(char c) { + char result = c; + for (int i = 0; i < rotors.length; i++) { + result = rotors[i].encryptBack(result); // Decrypt the character through the rotors + } + return result; + } + + public void setPositions(char[] positions) { + if (positions.length != 3) { + throw new IllegalArgumentException("Must provide exactly 3 positions"); // Validate input + } + this.initialPositions = positions.clone(); // Store initial positions + for (int i = 0; i < 3; i++) { + rotors[i].position = Character.toUpperCase(positions[i]) - 'A'; // Set rotor positions + } + } + + public void resetToInitialPosition() { + if (initialPositions != null) { + for (int i = 0; i < 3; i++) { + rotors[i].position = Character.toUpperCase(initialPositions[i]) - 'A'; // Reset to initial positions + } + } else { + for (int i = 0; i < 3; i++) { + rotors[i].position = 0; // Reset to position 0 if no initial positions are set + } + } + } +} \ No newline at end of file diff --git a/src/Classes/Hash.java b/src/Classes/Hash.java new file mode 100644 index 0000000..b124c5d --- /dev/null +++ b/src/Classes/Hash.java @@ -0,0 +1,11 @@ +package Classes; + +public abstract class Hash { + public abstract String calculateHash(String input); + + public void printHash(String input) { + System.out.println("Hash de \"" + input + "\": " + calculateHash(input)); + } +} + + diff --git a/src/Classes/HelpMenu.java b/src/Classes/HelpMenu.java new file mode 100644 index 0000000..807028c --- /dev/null +++ b/src/Classes/HelpMenu.java @@ -0,0 +1,193 @@ +package Classes; + +import java.util.Scanner; + +public class HelpMenu { + + // Main method to display the help menu and handle user navigation. + public static void displayMenuHelp(Scanner scanner) { + int choice; + + // Loop to display the menu until the user chooses to exit. + do { + System.out.println("\n========== HELP MENU =========="); + System.out.println("1. How to create a database?"); + System.out.println("2. How to encrypt my database password?"); + System.out.println("3. How the algorithms work ?"); + System.out.println("4. How to decrypt my password?"); + System.out.println("5. How to a add/ modify/ delete a site in my database?"); + System.out.println("6. Exit help menu"); + System.out.print("Select an option: "); + + // Read user's choice. + choice = scanner.nextInt(); + scanner.nextLine(); // Consume newline character. + + // Handle the user's choice using a switch statement. + switch (choice) { + case 1: + navigateCreateDatabaseHelp(scanner); // Navigate to database creation help. + break; + case 2: + navigateEncryptPasswordHelp(scanner); // Navigate to password encryption help. + break; + case 3: + navigateExplainAlgorithms(scanner); // Navigate to algorithm explanations. + break; + case 4: + navigateDecryptPasswordHelp(scanner); // Navigate to password decryption help. + break; + case 5: + navigateNewSiteHelp(scanner); + break; + case 6: + System.out.println("Exiting help menu..."); // Exit message. + Menu.main(null); // Return to the main menu. + break; + default: + System.out.println("Invalid choice. Please select a valid option."); // Handle invalid input. + } + } while (choice != 5); + + // Close the scanner when exiting the menu. + scanner.close(); + } + + // Method to provide help on creating a database. + private static void navigateCreateDatabaseHelp(Scanner scanner) { + boolean back = false; + do { + System.out.println("\n--- How to Create a Database ---"); + System.out.println("1. Open the application."); + System.out.println("2. Choose the 'Create a new database' option in the main menu."); + System.out.println("3. Enter a name for your database and a master password."); + System.out.println("4. The application will encrypt and store your database securely."); + System.out.println("5. Remember your master password; it cannot be recovered if lost."); + System.out.println("6. Go back"); + System.out.println("7. Exit Help Menu"); + System.out.print("Select an option: "); + + // Read the user's choice. + String choice = scanner.nextLine(); + if (choice.equalsIgnoreCase(String.valueOf(6))) { + back = true; // Go back to the main help menu. + } else if (choice.equalsIgnoreCase(String.valueOf(7))) { + System.out.println("Exiting help menu..."); + System.exit(0); // Exit the program. + } else { + System.out.println("Invalid input. Please choose 'Go back' or 'Exit'."); + } + } while (!back); + } + + // Method to provide help on encrypting database passwords. + private static void navigateEncryptPasswordHelp(Scanner scanner) { + boolean back = false; + do { + System.out.println("\n--- How to Encrypt My Database Password ---"); + System.out.println("1. When adding a password to your database, choose an encryption algorithm:"); + System.out.println(" - ROT(X): Shifts characters by X positions."); + System.out.println(" - RC4: A stream cipher for quick encryption."); + System.out.println(" - Vigenere: Uses a keyword to shift characters."); + System.out.println(" - Enigma: Simulates the historical encryption machine."); + System.out.println(" - Polybe: Encodes text using a 5x5 grid."); + System.out.println("2. The application will encrypt and save the password using the selected algorithm."); + System.out.println("3. Go back"); + System.out.println("4. Exit Help Menu"); + System.out.print("Select an option: "); + + String choice = scanner.nextLine(); + if (choice.equalsIgnoreCase(String.valueOf(3))) { + back = true; // Go back to the main help menu. + } else if (choice.equalsIgnoreCase(String.valueOf(4))) { + System.out.println("Exiting help menu..."); + System.exit(0); // Exit the program. + } else { + System.out.println("Invalid input. Please choose 'Go back' or 'Exit'."); + } + } while (!back); + } + + + + + // Method to explain encryption algorithms. + private static void navigateExplainAlgorithms(Scanner scanner) { + boolean back = false; + do { + System.out.println("\n--- Algorithm Explanations ---"); + System.out.println("1. ROT(X): Rotates each character by X positions in the alphabet."); + System.out.println(" - Example: 'A' with ROT(3) becomes 'D'."); + System.out.println("2. RC4: A symmetric key stream cipher that encrypts data byte by byte."); + System.out.println("3. Vigenere: Uses a keyword to shift letters cyclically."); + System.out.println(" - Example: Keyword 'KEY' applied to text shifts letters by positions derived from 'KEY'."); + System.out.println("4. Enigma: Mimics the encryption of the WWII Enigma machine."); + System.out.println("5. Polybe: Encodes text into pairs of numbers based on a 5x5 grid."); + System.out.println("6. Go back"); + System.out.println("7. Exit Help Menu"); + System.out.print("Select an option: "); + + String choice = scanner.nextLine(); + if (choice.equalsIgnoreCase(String.valueOf(6))) { + back = true; // Go back to the main help menu. + } else if (choice.equalsIgnoreCase(String.valueOf(7))) { + System.out.println("Exiting help menu..."); + System.exit(0); // Exit the program. + } else { + System.out.println("Invalid input. Please choose 'Go back' or 'Exit'."); + } + } while (!back); + } + + // Method to provide help on decrypting passwords. + private static void navigateDecryptPasswordHelp(Scanner scanner) { + boolean back = false; + do { + System.out.println("\n--- How to Decrypt My Password ---"); + System.out.println("1. Select the password you want to decrypt from the database."); + System.out.println("2. Enter the master password to access the database."); + System.out.println("3. The application will identify the encryption algorithm used."); + System.out.println("4. Enter any additional required keys or settings (e.g., keyword for Vigenere)."); + System.out.println("5. The password will be decrypted and displayed securely."); + System.out.println("6. Go back"); + System.out.println("7. Exit Help Menu"); + System.out.print("Select an option: "); + + String choice = scanner.nextLine(); + if (choice.equalsIgnoreCase(String.valueOf(6))) { + back = true; // Go back to the main help menu. + } else if (choice.equalsIgnoreCase(String.valueOf(7))) { + System.out.println("Exiting help menu..."); + System.exit(0); // Exit the program. + } else { + System.out.println("Invalid input. Please choose 'Go back' or 'Exit'."); + } + } while (!back); + } + + // Method to provide help on adding/ modifying/ deleting a site in the database + private static void navigateNewSiteHelp(Scanner scanner) { + boolean back = false; + do { + System.out.println("\n--- How to Add/ Modify/ Delete a Site in my database ---"); + System.out.println("1. In the main menu select : 'Choose an existing database'"); + System.out.println("2. Connect to your database where you have your password"); + System.out.println("3. Select 'Manage sites'"); + System.out.println("4. Select 'Add a new site'/ 'Modify a site' or 'Delete a site'"); + System.out.println("5. Follow the instructions"); + System.out.println("6. Go back"); + System.out.println("7. Exit Help Menu"); + System.out.print("Select an option: "); + + String choice = scanner.nextLine(); + if (choice.equalsIgnoreCase(String.valueOf(6))) { + back = true; // Go back to the main help menu. + } else if (choice.equalsIgnoreCase(String.valueOf(7))) { + System.out.println("Exiting help menu..."); + System.exit(0); // Exit the program. + } else { + System.out.println("Invalid input. Please choose 'Go back' or 'Exit'."); + } + } while (!back); + } +} diff --git a/src/Classes/Lfsr.java b/src/Classes/Lfsr.java new file mode 100644 index 0000000..4abebe3 --- /dev/null +++ b/src/Classes/Lfsr.java @@ -0,0 +1,77 @@ +package Classes; + +public class Lfsr { + char state; + char taps; + + public Lfsr(char state, char taps) { + this.state = state; //represents the current state of the LFSR as a 16-bit integer (char) + this.taps = taps; //mask indicating the positions (bits) for feedback + } + //method calculating the parity of a number (number of bits set to 1) to determine the feedback bit + public static int parity(int mask) { + int p = 0; + for ( ; mask > 0 ; mask >>>= 1) + p ^= mask & 1; + return p; + } + //method converting an integer to a 16-bit binary string + public static String convertor(int registers) { + return String.format("%16s", + Integer.toBinaryString(registers)).replace(" ", "0"); //displays a 16-bit string, keeping the leading zero if it is 0 + } + + public String fibonacci(int loops) { + String alea = ""; + for (int i = 0 ; i < loops ; i++) { + System.out.println("Debug info: state = " + convertor(this.state)); + int bit = Lfsr.parity(this.state & this.taps); //calculates the mask from the XOR + System.out.println("bit = " + bit); + alea += this.state & 1; // retrieves the least significant bit + this.state >>= 1; //shifts the bits to the right + this.state |= bit << 15; //sets the least significant bit 15 positions to the left (last bit) + } + return alea; + } + + public String galois(int loops) { + String alea = ""; + for (int i = 0 ; i < loops ; i++) { + System.out.println("Debug info: state = " + convertor(this.state)); + int bit = this.state & 1; //retrieves the least significant bit + alea += bit; + this.state >>= 1; //shifts the bits to the right + if (bit == 1) //if the least significant bit is 1, modifies the state with the XOR gate + this.state ^= this.taps; + } + return alea; + } + + + /*** + * + * + * @param tapsHex a value indicating the bit position in hex + * @param loops number of loops for the galois and fibonacci methods + * @param seedHex seed for the algorithm + * @param mode choice between the two methods galois and fibonacci + * @return a result (alea) in 16 bits + */ + public String lfsr(String tapsHex, int loops, String seedHex, String mode) { + char taps = (char) Integer.parseInt(tapsHex, 16); + char seed = (char) Integer.parseInt(seedHex, 16); + + this.state = seed; + this.taps = taps; + + String result; + if (mode.equalsIgnoreCase("galois")) { //Executes the method based on the chosen transformation function + result = this.galois(loops); + } else { + result = this.fibonacci(loops); + } + + return result; + } + +} diff --git a/src/Classes/MD5.java b/src/Classes/MD5.java new file mode 100644 index 0000000..3864484 --- /dev/null +++ b/src/Classes/MD5.java @@ -0,0 +1,57 @@ +package Classes; + +import java.security.MessageDigest; +import java.nio.charset.StandardCharsets; + +public class MD5 extends Hash { + /** + * Computes the MD5 hash of a string. + * @param input The string to hash + * @return The MD5 hash in hexadecimal (32 characters) + * @throws IllegalArgumentException if input is null + * @throws RuntimeException if an error occurs during hashing + */ + @Override + public String calculateHash(String input) { + if (input == null) { + throw new IllegalArgumentException("Input cannot be null"); + } + + try { + // Get an instance of MessageDigest configured for MD5 + // MessageDigest is a class that implements the hashing algorithm + MessageDigest md = MessageDigest.getInstance("MD5"); + + // Convert the input string to a byte array using UTF-8 encoding + // then compute the MD5 hash of these bytes + // The result is a 16-byte array (128 bits, standard MD5 size) + byte[] messageDigest = md.digest(input.getBytes(StandardCharsets.UTF_8)); + + // Create a StringBuilder to build the hexadecimal representation + // It will be more efficient than a simple String for concatenations + StringBuilder hexString = new StringBuilder(); + + // For each byte of the MD5 hash + for (byte b : messageDigest) { + // String.format("%02x", b & 0xff) does several things: + // 1. b & 0xff: converts the byte to an unsigned integer (0-255) + // because bytes in Java are signed (-128 to 127) + // 2. %02x: formats the integer in hexadecimal with 2 positions + // - % indicates the start of the format + // - 0 indicates to pad with zeros + // - 2 indicates the minimum width + // - x indicates lowercase hexadecimal notation + hexString.append(String.format("%02x", b & 0xff)); + } + + // Final conversion of the StringBuilder to a String + // The result will be a 32-character hexadecimal string + return hexString.toString(); + + } catch (Exception e) { + // In case of an error (very rare as MD5 is a standard algorithm) + // we encapsulate the exception in a RuntimeException + throw new RuntimeException("Error computing MD5 hash", e); + } + } +} \ No newline at end of file diff --git a/src/Classes/Menu.java b/src/Classes/Menu.java new file mode 100644 index 0000000..f191ea5 --- /dev/null +++ b/src/Classes/Menu.java @@ -0,0 +1,211 @@ +package Classes; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +public class Menu { + private static final String[] LOGO_LINES = { + "________ ___ ___ _________ _______ ___ __ ________ ________ ________ _______ ________ ", + "|\\ __ \\ |\\ \\ / /|\\___ ___\\\\ ___ \\ |\\ \\ |\\ \\|\\ __ \\|\\ __ \\|\\ ___ \\|\\ ___ \\ |\\ ___ \\ ", + "\\ \\ \\|\\ /_ \\ \\ \\/ / ||___ \\ \\_\\ \\ __/|\\ \\ \\ \\ \\ \\ \\ \\|\\ \\ \\ \\|\\ \\ \\ \\_|\\ \\ \\ __/|\\ \\ \\\\ \\ \\ ", + " \\ \\ __ \\ \\ \\ / / \\ \\ \\ \\ \\ \\_|/_\\ \\ \\ __\\ \\ \\ \\ __ \\ \\ _ _\\ \\ \\ \\\\ \\ \\ \\_|/_\\ \\ \\\\ \\ \\ ", + " \\ \\ \\|\\ \\ \\/ / / \\ \\ \\ \\ \\ \\_|\\ \\ \\ \\|\\__\\_\\ \\ \\ \\ \\ \\ \\ \\\\ \\\\ \\ \\_\\\\ \\ \\ \\_|\\ \\ \\ \\\\ \\ \\ ", + " \\ \\_______\\__/ / / \\ \\__\\ \\ \\_______\\ \\____________\\ \\__\\ \\__\\ \\__\\\\ _\\\\ \\_______\\ \\_______\\ \\__\\\\ \\__\\", + " \\|_______|\\___/ / \\|__| \\|_______|\\|____________|\\|__|\\|__|\\|__|\\|__|\\|_______|\\|_______|\\|__| \\|__|", + " \\|___|/ " + }; + + private static void animateLogo() { + try { + // Clear console + System.out.print("\033[H\033[2J"); + System.out.flush(); + + // Create a buffer to store the current state of the logo + char[][] buffer = new char[LOGO_LINES.length][]; + for (int i = 0; i < LOGO_LINES.length; i++) { + buffer[i] = new char[LOGO_LINES[i].length()]; + for (int j = 0; j < LOGO_LINES[i].length(); j++) { + buffer[i][j] = ' '; + } + } + + // Animate each character + for (int i = 0; i < LOGO_LINES.length; i++) { + for (int j = 0; j < LOGO_LINES[i].length(); j++) { + buffer[i][j] = LOGO_LINES[i].charAt(j); + + // Clear screen and redraw the entire buffer + System.out.print("\033[H\033[2J"); + System.out.flush(); + + // Print current state of buffer + for (int k = 0; k < buffer.length; k++) { + System.out.println("\033[1;32m" + new String(buffer[k]) + "\033[0m"); + } + + TimeUnit.MILLISECONDS.sleep(1); + } + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + private static void matrixRain() { + try { + String[] matrix = {"ア", "イ", "ウ", "エ", "オ", "カ", "キ", "ク", "ケ", "コ", "タ", "チ", "ツ", "テ", "ト", "ナ", + "1", "0", "@", "#", "$", "%", "&", "*"}; + Random rand = new Random(); + + String[] buffer = new String[10]; + for (int i = 0; i < buffer.length; i++) { + buffer[i] = ""; + } + + for (int i = 0; i < 20; i++) { // Reduced iterations for faster startup + for (int j = buffer.length - 1; j > 0; j--) { + buffer[j] = buffer[j-1]; + } + + StringBuilder newLine = new StringBuilder(); + for (int j = 0; j < 50; j++) { + newLine.append(matrix[rand.nextInt(matrix.length)]).append(" "); + } + buffer[0] = newLine.toString(); + + System.out.print("\033[H\033[2J"); + System.out.flush(); + + for (String line : LOGO_LINES) { + System.out.println("\033[1;32m" + line + "\033[0m"); + } + System.out.println(); + + for (String line : buffer) { + System.out.println("\033[0;32m" + line + "\033[0m"); + } + + TimeUnit.MILLISECONDS.sleep(100); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + private static void showSplashScreen() { + animateLogo(); + matrixRain(); + System.out.print("\033[H\033[2J"); // Clear screen before showing menu + System.out.flush(); + } + + public static void main(String[] args) { + // Show splash screen + showSplashScreen(); + + // Initialize the scanner for user input + Scanner scanner = new Scanner(System.in); + // Load the database file to manage existing databases + File databasesFile = new File("databases.json"); + DatabasesManager dbManager = new DatabasesManager(databasesFile); + + // Your existing menu code starts here + System.out.println("Welcome to the Encryption/Decryption Program"); + System.out.println("Choose an option:"); + System.out.println("1. Choose an existing database"); + System.out.println("2. Create a new database"); + System.out.println("3. Help Menu"); + + // Read the user's choice for the menu + int dbChoice = scanner.nextInt(); + scanner.nextLine(); // Consume the newline character left by nextInt + + if (dbChoice == 1) { + System.out.println("Enter the name of the database:"); + String dbName = scanner.nextLine(); + System.out.println("Enter the password:"); + String inputPassword = scanner.nextLine(); + + if (dbManager.verifyDatabase(dbName, inputPassword)) { + System.out.println("Successfully connected to the database: " + dbName); + Map encryptionMap = dbManager.getEncryptionMap(dbName); + SiteManager siteManager = new SiteManager(new File(dbName + ".json"), encryptionMap); + siteManager.manageSites(scanner); + } else { + System.out.println("Incorrect database name or password."); + } + } + else if (dbChoice == 2) { + System.out.println("Enter the name of the new database:"); + String dbName = scanner.nextLine(); + System.out.println("Choose a password option:"); + System.out.println("1. Enter a custom password"); + System.out.println("2. Generate a random password"); + + int passwordChoice = scanner.nextInt(); + scanner.nextLine(); + + String password = passwordChoice == 1 + ? scanner.nextLine() + : PasswordUtils.generateRandomPassword(12); + + System.out.println("Generated password: " + password); + + // Ask the user for encryption methods + Map encryptionMap = new HashMap<>(); + boolean addMoreEncryptions = true; + + while (addMoreEncryptions) { + System.out.println("Choose an encryption method:"); + System.out.println("1. RotX"); + System.out.println("2. RC4"); + System.out.println("3. Vigenere"); + System.out.println("4. Polybios"); + System.out.println("5. Done adding encryptions"); + + int encryptionChoice = scanner.nextInt(); + scanner.nextLine(); + + switch (encryptionChoice) { + case 1 -> { + System.out.println("Enter the shift value for RotX:"); + String shiftValue = scanner.nextLine(); + encryptionMap.put("RotX", shiftValue); + } + case 2 -> { + System.out.println("Enter the key for RC4:"); + String rc4Key = scanner.nextLine(); + encryptionMap.put("RC4", rc4Key); + } + case 3 -> { + System.out.println("Enter the key for Vigenere:"); + String vigenereKey = scanner.nextLine(); + encryptionMap.put("Vigenere", vigenereKey); + } + case 4 -> { + encryptionMap.put("Polybios", "default"); + } + case 5 -> addMoreEncryptions = false; + default -> System.out.println("Invalid choice. Please choose a valid encryption method."); + } + } + + // Create the new database with the given name, password, and encryption map + + dbManager.createDatabase(dbName, password, encryptionMap); + SiteManager siteManager = new SiteManager(new File(dbName + ".json"), encryptionMap); + siteManager.manageSites(scanner); + } + else if (dbChoice == 3) { + HelpMenu.displayMenuHelp(scanner); + } + else { + System.out.println("Invalid choice."); + } + } +} \ No newline at end of file diff --git a/src/Classes/PasswordUtils.java b/src/Classes/PasswordUtils.java new file mode 100644 index 0000000..71a5283 --- /dev/null +++ b/src/Classes/PasswordUtils.java @@ -0,0 +1,18 @@ +package Classes; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Random; + +public class PasswordUtils { + + public static String generateRandomPassword(int length) { + String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()"; + Random random = new Random(); + StringBuilder password = new StringBuilder(); + for (int i = 0; i < length; i++) { + password.append(chars.charAt(random.nextInt(chars.length()))); // generate random password in range chars, from random int + } + return password.toString(); + } +} diff --git a/src/Classes/PolybSquareEncrypter.java b/src/Classes/PolybSquareEncrypter.java new file mode 100644 index 0000000..c0c6aef --- /dev/null +++ b/src/Classes/PolybSquareEncrypter.java @@ -0,0 +1,118 @@ +package Classes; + +/** + * Class used to encrypt and decrypt string of text using polyb square algorithm. + * @remarks String must be only composed of letters. + * @remarks All space character will not be taken into account. + */ +public class PolybSquareEncrypter { + + // Fields. + /** + * The layout choose by the user for the polyb table encrypter. + */ + private final PolybSquareLayout LayoutChoice; + + /** + * The current polybTable used for this encrypter. + */ + private final char[][] PolybSquareTable; + + // Constructor. + public PolybSquareEncrypter(PolybSquareLayout layoutChoice) { + this.LayoutChoice = layoutChoice; + this.PolybSquareTable = new PolybSquareLayoutCollection().getPolybSquareLayout(layoutChoice); + } + + // Functions. + /** + * Get all two-digits numbers corresponding to each character of the text to encrypt. + * @param plainText The text to encrypt. + */ + public final String encrypt(String plainText) { + // The succession of numbers (XY) recovered according to the characters of the plain text to encode. + StringBuilder encryptedCharacters = new StringBuilder(""); + + for (int i = 0;i < plainText.length(); i++){ + // Polyb table contain 25 value. In consequence, we decide to interpret w as two v character. + if (plainText.charAt(i) == 'w'){ + encryptedCharacters.append(findPolybSquareCode('v')); + encryptedCharacters.append(findPolybSquareCode('v')); + } + else if (Character.isLetter(plainText.charAt(i))) // must be a letter + { + String code = findPolybSquareCode(plainText.charAt(i)); + encryptedCharacters.append(code); + } + else // other character such as number, special, space character are ignored. + { + break; + } + } + + // End of encryption. + return encryptedCharacters.toString(); + } + + /** Get the number corresponding to the coordinate of the character in the current polyb table. + * @param characterToFind the character to find in polyb table. + * @return a two digits number. + * @remarks First digit is the X coordinate in the polyb table, second is Y. + */ + private final String findPolybSquareCode(char characterToFind) { + // for each col -> ordinate Y + for (int i = 0; i < PolybSquareTable.length; i++) { + // for each row -> abscyssa X + for (int j = 0; j < PolybSquareTable[i].length; j++) { + // if the character at this position in the table match, get his code + if (PolybSquareTable[i][j] == characterToFind) { + // XY + // Add one to col and row value in order to start to 1 and not 0. + return String.valueOf(j + 1) + String.valueOf(i + 1); + } + } + } + // If no match is found, instead of throwing error, we use a blank character. + System.out.println("Unrecognized character"); + return null; + //return "__"; + } + + /** + * Get all plain text string from text encrypted with PolybSquareMethod. + * @param encryptedText An encrypted string only composed of numbers. + * @return A plain text string. + * @remarks the succession of numbers of the encrypted text is a succession of two-digits numbers (XXYYZZ...) + */ + public final String decrypt(String encryptedText) + { + StringBuilder decryptedCharacters = new StringBuilder(""); + + // Get every pair of two-digits numbers composing the encrypted string. + for (int i = 0;i < encryptedText.length(); i+=2) { + char firstChar = encryptedText.charAt(i); + char secondChar = encryptedText.charAt(i+1); + + // Get the latin alphabet letter corresponding to this two-digits number. + decryptedCharacters.append(getCharacterFromPolybSquareCode(firstChar, secondChar)); + } + + // End of decryption. + return decryptedCharacters.toString(); + } + + /** + * Get one latin alphabet character using two number as coordinate in the polyb square table. + * @param firstChar first number, used as abscyssa X + * @param secondChar second number used as ordinate, Y + * @return One latin alphabet character. + */ + private final char getCharacterFromPolybSquareCode(char firstChar, char secondChar) { + // cast the char received into int values + int x = Character.getNumericValue(firstChar); + int y = Character.getNumericValue(secondChar); + // two-dimensional array are manipulated such as 2dArray[Y][X] + // - 1 beccause table index start at 0 and not 1. + return PolybSquareTable[y - 1][x - 1]; + } +} diff --git a/src/Classes/PolybSquareLayout.java b/src/Classes/PolybSquareLayout.java new file mode 100644 index 0000000..c6d7afe --- /dev/null +++ b/src/Classes/PolybSquareLayout.java @@ -0,0 +1,12 @@ +package Classes; + +/** + * PolybSquare Encrypter algorithm can use different layout for the table. + * Those enum value represent the different possible layouts selectable by user. + */ +public enum PolybSquareLayout { + HORIZONTAL, + VERTICAL, + //CLOCKWISE_SPIRAL, + //COUNTERCLOCKWISE_SPIRAL +} \ No newline at end of file diff --git a/src/Classes/PolybSquareLayoutCollection.java b/src/Classes/PolybSquareLayoutCollection.java new file mode 100644 index 0000000..0318031 --- /dev/null +++ b/src/Classes/PolybSquareLayoutCollection.java @@ -0,0 +1,58 @@ +package Classes; + +import java.util.Dictionary; +import java.util.Hashtable; + +/** + * The class managing access to all the possible layout for the polybSquare Encrypter. + */ +public class PolybSquareLayoutCollection { + + // Fields. + /** + * All character of the latin alphabet are listed from a to z in successive rows + * top to bottom. + */ + private final char[][] horizontal = { + {'a','b','c','d','e'}, + {'f','g','h','i','j'}, + {'k','l','m','n','o'}, + {'p','q','r','s','t'}, + {'u','v','x','y','z'}, + }; + + /** + * All character of the latin alphabet are listed from a to z in successive cols + * left to right. + */ + private final char[][] vertical = { + {'a','f','k','p','u'}, + {'b','g','l','q','v'}, + {'c','h','m','r','x'}, + {'d','i','n','s','y'}, + {'e','j','o','t','z'}, + }; + + /** + * The collection of all the possible layouts. + */ + private final Dictionary polybSquareTables = new Hashtable<>(); + + // Constructor. + public PolybSquareLayoutCollection() { + polybSquareTables.put(PolybSquareLayout.VERTICAL, vertical); + polybSquareTables.put(PolybSquareLayout.HORIZONTAL, horizontal); + } + + // Functions. + /** + * Getter function for a polyb square table layout. + * @param alphabet the enum value corresponding to the possible layout selectable by user. + * @return a 2D array representing the polybSquare table. + */ + public char[][] getPolybSquareLayout(PolybSquareLayout alphabet) + { + // TODO Add error management + return polybSquareTables.get(alphabet); + } +} diff --git a/src/Classes/RC4.java b/src/Classes/RC4.java new file mode 100644 index 0000000..dd9f3c7 --- /dev/null +++ b/src/Classes/RC4.java @@ -0,0 +1,105 @@ +package Classes; + +public class RC4 { + + private int[] S = new int[256]; + private int[] T = new int[256]; + public String key = ""; + + // Initialization of S and T arrays with the key + public void init(String key) { + if (key == null || key.isEmpty()) { + throw new IllegalArgumentException("The key cannot be null or empty"); + } + this.key = key; + + // Initialize S and T arrays + for (int i = 0; i < 256; i++) { + S[i] = i; + T[i] = key.charAt(i % key.length()); + } + + // Initial permutation of S + int j = 0; + for (int i = 0; i < 256; i++) { + j = (j + S[i] + T[i]) & 0xFF; + swap(i, j); + } + } + + // Encrypt the plaintext + public String encrypt(String clearText) { + if (clearText == null || clearText.isEmpty()) { + throw new IllegalArgumentException("The text to encrypt cannot be null or empty"); + } + + // Reinitialize to ensure a clean state + init(this.key); + + StringBuilder encrypted = new StringBuilder(); + int i = 0, j = 0; + + // For each character in the plaintext + for (int n = 0; n < clearText.length(); n++) { + char c = clearText.charAt(n); + // Increment i modulo 256 + i = (i + 1) & 0xFF; + // Increment j modulo 256 + j = (j + S[i]) & 0xFF; + // Swap S[i] and S[j] + swap(i, j); + // Calculate t + int t = (S[i] + S[j]) & 0xFF; + // Get the value of S[t] + int k = S[t]; + // Encrypt the character + int encryptedByte = c ^ k; + + // Convert to binary with zero padding + encrypted.append(String.format("%8s", + Integer.toBinaryString(encryptedByte & 0xFF)) + .replace(' ', '0')); + + // Add space except for the last character + if (n < clearText.length() - 1) { + encrypted.append(" "); + } + } + + return encrypted.toString(); + } + + // Decrypt the encrypted text + public String decrypt(String encryptedText) { + if (encryptedText == null || encryptedText.isEmpty()) { + throw new IllegalArgumentException("The encrypted text cannot be null or empty"); + } + + // Reinitialize to ensure a clean state + init(this.key); + + StringBuilder decrypted = new StringBuilder(); + String[] binaryStrings = encryptedText.split(" "); + int i = 0, j = 0; + + // For each byte in the encrypted text + for (String binaryString : binaryStrings) { + char c = (char) Integer.parseInt(binaryString, 2); + i = (i + 1) & 0xFF; + j = (j + S[i]) & 0xFF; + swap(i, j); + int t = (S[i] + S[j]) & 0xFF; + int k = S[t]; + decrypted.append((char) (c ^ k)); + } + + return decrypted.toString(); + } + + // Swap function to exchange two elements in the S array + private void swap(int i, int j) { + int temp = S[i]; + S[i] = S[j]; + S[j] = temp; + } +} \ No newline at end of file diff --git a/src/Classes/ROTX.java b/src/Classes/ROTX.java new file mode 100644 index 0000000..1f77f24 --- /dev/null +++ b/src/Classes/ROTX.java @@ -0,0 +1,58 @@ +package Classes; + +public class ROTX { + + // Encryption function + public static String encryptROT(String input, int x) { + StringBuilder encryptedString = new StringBuilder(); + + // Normalisation of X to be in the interval [0, 25] + x = x % 26; // ensure that x is in [0, 25] + if (x < 0) { + x += 26; // IF X is negative we adjust it to be positive + } + // Iterate through each character of the string + for (char character : input.toCharArray()) { + // Check if the character is a letter (uppercase or lowercase) + if (Character.isLetter(character)) { + // Determine the base depending on whether the letter is uppercase or lowercase + char base = Character.isLowerCase(character) ? 'a' : 'A'; + // Apply ROT(X) encryption + char encryptedChar = (char) ((character - base + x) % 26 + base); + encryptedString.append(encryptedChar); + } else { + // Append non-alphabetic characters without changing them + encryptedString.append(character); + } + } + + return encryptedString.toString(); + } + + // Decryption function + public static String decryptROT(String input, int x) { + StringBuilder decryptedString = new StringBuilder(); + + // Normalisation of X to be in the interval [0, 25] + x = x % 26; // ensure that x is in [0, 25] + if (x < 0) { + x += 26; // IF X is negative we adjust it to be positive + } + // Iterate through each character of the string + for (char character : input.toCharArray()) { + // Check if the character is a letter (uppercase or lowercase) + if (Character.isLetter(character)) { + // Determine the base depending on whether the letter is uppercase or lowercase + char base = Character.isLowerCase(character) ? 'a' : 'A'; + // Apply ROT(X) decryption (subtract x instead of adding it) + char decryptedChar = (char) ((character - base - x + 26) % 26 + base); + decryptedString.append(decryptedChar); + } else { + // Append non-alphabetic characters without changing them + decryptedString.append(character); + } + } + + return decryptedString.toString(); + } +} diff --git a/src/Classes/Sha256.java b/src/Classes/Sha256.java new file mode 100644 index 0000000..b704c12 --- /dev/null +++ b/src/Classes/Sha256.java @@ -0,0 +1,27 @@ +package Classes; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class Sha256 extends Hash { + + // function to calculate hash + @Override + public String calculateHash(String input) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); //create instance of sha256 + byte[] hashBytes = digest.digest(input.getBytes()); //hash input bytes + + StringBuilder hexString = new StringBuilder(); + for (byte b : hashBytes) { + String hex = Integer.toHexString(0xff & b); //format byte in hex string + if (hex.length() == 1) hexString.append('0'); + hexString.append(hex); //concatenate bytes + } + return hexString.toString(); //return hash string + + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Error :" + , e); + } + } +} \ No newline at end of file diff --git a/src/Classes/SiteManager.java b/src/Classes/SiteManager.java new file mode 100644 index 0000000..46061dd --- /dev/null +++ b/src/Classes/SiteManager.java @@ -0,0 +1,224 @@ +package Classes; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.*; + +public class SiteManager { + private final File dbFile; + private final List> sites; + private final Map encryptionMap; // Méthodes de chiffrement associées + + public SiteManager(File dbFile, Map encryptionMap) { + this.dbFile = dbFile; //file name + this.encryptionMap = encryptionMap;// encryption method with parameter needed + this.sites = loadSites(); + } + + public void manageSites(Scanner scanner) { //methods to ask user what method he will call + while (true) { + System.out.println("Choose an action:"); + System.out.println("1. Add a site"); + System.out.println("2. Modify a site"); + System.out.println("3. Delete a site"); + System.out.println("4. Display all sites"); + System.out.println("5. Exit"); + int choice = scanner.nextInt(); + scanner.nextLine(); + + switch (choice) { //calls method based on the user input + case 1 -> addSite(scanner); + case 2 -> modifySite(scanner); + case 3 -> deleteSite(scanner); + case 4 -> displaySites(); + case 5 -> { return; } + default -> System.out.println("Invalid choice."); + } + } + } + + public void addSite(String siteName, String username, String password) {//add method use for applying value from scanner method and for the test file + Map site = new HashMap<>(); + site.put("siteName", siteName); + site.put("username", username); + site.put("password", password); + sites.add(site); + saveSites(); + } + + public void addSite(Scanner scanner) { //add site in the selected json + System.out.println("Enter site name:"); + String siteName = scanner.nextLine(); + System.out.println("Enter username:"); + String username = scanner.nextLine(); + System.out.println("Enter password:"); + String password = scanner.nextLine(); + addSite(siteName, username, password); + } + + public void modifySite(String siteName, String newUsername, String newPassword) { //modify method use for applying value from scanner method and for the test file + for (Map site : sites) { + if (site.get("siteName").equals(siteName)) { + if (newUsername != null && !newUsername.isEmpty()) { + site.put("username", newUsername); + } + if (newPassword != null && !newPassword.isEmpty()) { + site.put("password", newPassword); + } + saveSites(); + return; + } + } + throw new IllegalArgumentException("Site not found: " + siteName); + } + + public void modifySite(Scanner scanner) { //modify the selected site from the selected json + System.out.println("Enter the site name to modify:"); + String siteName = scanner.nextLine(); + System.out.println("Enter the new username (leave empty to keep current):"); + String newUsername = scanner.nextLine(); + System.out.println("Enter the new password (leave empty to keep current):"); + String newPassword = scanner.nextLine(); + modifySite(siteName, newUsername, newPassword); + } + + public void deleteSite(String siteName) { //delete method use for the test file + sites.removeIf(site -> site.get("siteName").equals(siteName)); + saveSites(); + } + + public void deleteSite(Scanner scanner) { //delete the selected site from the selected json + System.out.println("Enter the site name to delete:"); + String siteName = scanner.nextLine(); + deleteSite(siteName); + } + + public void displaySites() { // displays all sites from selected json + if (sites.isEmpty()) { + System.out.println("No sites available."); + } else { + for (Map site : sites) { + String siteName = site.get("siteName"); + String username = site.get("username"); + String password = site.get("password"); + System.out.println("Site Name: " + siteName); + System.out.println("Username: " + username); + System.out.println("Password: " + password); + System.out.println("-----------------------------"); + } + } + } + + public List> loadSites() { + if (!dbFile.exists()) return new ArrayList<>(); + try (FileReader reader = new FileReader(dbFile)) { + // read file and decrypt + StringBuilder encryptedContent = new StringBuilder(); + int c; + while ((c = reader.read()) != -1) { + encryptedContent.append((char) c); + } + + // decrypt the file content + String decryptedContent = decrypt(encryptedContent.toString()); + + Gson gson = new Gson(); + Map data = gson.fromJson(decryptedContent, Map.class); + return (List>) data.get("sites"); + } catch (IOException e) { + e.printStackTrace(); + return new ArrayList<>(); + } + } + + public void saveSites() { + try (FileWriter writer = new FileWriter(dbFile)) { + // convert sites into java json file + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + Map data = Map.of("sites", sites); + String jsonContent = gson.toJson(data); + + // crypt json content + String encryptedContent = encrypt(jsonContent); + + writer.write(encryptedContent); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private String encrypt(String input) { + String result = input; + //all case for all encryption methods + for (String method : encryptionMap.keySet()) { + switch (method) { + case "RotX" -> { + //rotx encryption + int shift = Integer.parseInt(encryptionMap.get("RotX")); //get encryption value from the encryption map from databases.json + result = ROTX.encryptROT(result, shift); + } + case "RC4" -> { + //rc4 encryption + + RC4 rc4 = new RC4(); + String key = encryptionMap.get("RC4");//get encryption value from the encryption map from databases.json + rc4.init(key); + result = rc4.encrypt(result); + } + case "Vigenere" -> { + //vigenere encryption + + String key = encryptionMap.get("Vigenere");//get encryption value from the encryption map from databases.json + result = VigenereAlgo.encrypt(result, key); + } + case "Polybios" -> { + } + case "AES" -> { + } + } + } + return result; + } + + private String decrypt(String input) { + String result = input; + // invert encryption order to handle the multiple encryption + List methods = new ArrayList<>(encryptionMap.keySet()); + Collections.reverse(methods); + + for (String method : methods) { + switch (method) { + case "RotX" -> { + //decrypt json content from databases RotX encryptionmethod + int shift = Integer.parseInt(encryptionMap.get("RotX")); + result = ROTX.decryptROT(result, shift); + } + case "RC4" -> { + //decrypt json content from databases RC4 encryptionmethod + + RC4 rc4 = new RC4(); + String key = encryptionMap.get("RC4"); + rc4.init(key); + result = rc4.decrypt(result); + } + case "Vigenere" -> { + //decrypt json content from databases Vigenere encryptionmethod + + String key = encryptionMap.get("Vigenere"); + result = VigenereAlgo.decrypt(result, key); + } + case "Polybios" -> { + } + case "AES" -> { + } + } + } + return result; + } + +} diff --git a/src/Classes/Steganography/Image.java b/src/Classes/Steganography/Image.java new file mode 100644 index 0000000..77f0f1c --- /dev/null +++ b/src/Classes/Steganography/Image.java @@ -0,0 +1,134 @@ +package Classes.Steganography; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import javax.imageio.ImageIO; + +public class Image extends Steganography { + private BufferedImage image; + private String imagePath; + + public Image(String imagePath) { + this.imagePath = imagePath; + try { + this.image = ImageIO.read(new File(imagePath)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void encode(String message) { + if (image == null || message == null) { + throw new IllegalArgumentException("Image or message is null"); + } + + // Add message length at the beginning + message = message.length() + ":" + message; + + // Convert message to binary + byte[] messageBytes = message.getBytes(); + int messageLength = messageBytes.length; + + // Check if image can hold the message + if (messageLength * 8 > image.getWidth() * image.getHeight()) { + throw new IllegalArgumentException("Message too large for this image"); + } + + int messageIndex = 0; + int bitIndex = 0; + + // Iterate through image pixels + for (int y = 0; y < image.getHeight() && messageIndex < messageLength; y++) { + for (int x = 0; x < image.getWidth() && messageIndex < messageLength; x++) { + int pixel = image.getRGB(x, y); + + if (bitIndex == 8) { + messageIndex++; + bitIndex = 0; + } + + if (messageIndex < messageLength) { + // Get current byte from message + byte currentByte = messageBytes[messageIndex]; + + // Get current bit from byte + int messageBit = (currentByte >> (7 - bitIndex)) & 1; + + // Modify blue channel + int blue = pixel & 0xff; + blue = (blue & 0xfe) | messageBit; + + // Update pixel + pixel = (pixel & 0xffffff00) | blue; + image.setRGB(x, y, pixel); + + bitIndex++; + } + } + } + + // Save encoded image + try { + String outputPath = imagePath.substring(0, imagePath.lastIndexOf('.')) + "_encoded.png"; + ImageIO.write(image, "png", new File(outputPath)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public String decode() { + if (image == null) { + throw new IllegalArgumentException("No image loaded"); + } + + StringBuilder binary = new StringBuilder(); + StringBuilder message = new StringBuilder(); + int count = 0; + + // First, extract the message length + StringBuilder lengthStr = new StringBuilder(); + boolean foundDelimiter = false; + + outer: + for (int y = 0; y < image.getHeight(); y++) { + for (int x = 0; x < image.getWidth(); x++) { + int pixel = image.getRGB(x, y); + int blue = pixel & 0xff; + binary.append(blue & 1); + + if (binary.length() == 8) { + int ascii = Integer.parseInt(binary.toString(), 2); + char character = (char) ascii; + binary.setLength(0); + + if (!foundDelimiter) { + if (character == ':') { + foundDelimiter = true; + count = Integer.parseInt(lengthStr.toString()); + } else { + lengthStr.append(character); + } + } else { + message.append(character); + if (message.length() == count) { + break outer; + } + } + } + } + } + + return message.toString(); + } + + public BufferedImage getImage() { + return image; + } + + public void setImage(BufferedImage image) { + this.image = image; + } +} \ No newline at end of file diff --git a/src/Classes/Steganography/Steganography.java b/src/Classes/Steganography/Steganography.java new file mode 100644 index 0000000..69b89ba --- /dev/null +++ b/src/Classes/Steganography/Steganography.java @@ -0,0 +1,25 @@ +package Classes.Steganography; + +import java.io.IOException; + +public class Steganography { + protected String content; + + public void encode(String message) throws IOException { + } + + public String decode() throws IOException { + return null; + } + + public String getContent() { + return this.content; + } + + public void setContent(String content) { + if (content == null) { + throw new IllegalArgumentException("Content cannot be null"); + } + this.content = content; + } +} \ No newline at end of file diff --git a/src/Classes/Steganography/Text.java b/src/Classes/Steganography/Text.java new file mode 100644 index 0000000..670f874 --- /dev/null +++ b/src/Classes/Steganography/Text.java @@ -0,0 +1,100 @@ +package Classes.Steganography; + +public class Text extends Steganography { + private String stegoText; + private String content; + + @Override + public void encode(String message) { + validateMessage(message); + validateContent(); + + String binary = convertToBinary(message); + this.stegoText = embedMessage(binary); + } + + private void validateMessage(String message) { + if (message == null || message.isEmpty()) { + throw new IllegalArgumentException("Message cannot be empty"); + } + } + + private void validateContent() { + if (content == null || content.trim().isEmpty()) { + throw new IllegalStateException("Content cannot be empty"); + } + } + + private String convertToBinary(String message) { + StringBuilder binary = new StringBuilder(); + for (char c : message.toCharArray()) { + String binaryChar = String.format("%8s", Integer.toBinaryString(c)) + .replace(' ', '0'); + binary.append(binaryChar); + } + return binary.toString(); + } + + private String embedMessage(String binary) { + String[] words = content.split("\\s+"); + int requiredSpaces = binary.length(); + + if (words.length - 1 < requiredSpaces) { + throw new IllegalStateException( + "Content is too short to encode the message. Need at least " + + (requiredSpaces + 1) + " words, but got " + words.length); + } + + StringBuilder result = new StringBuilder(); + for (int i = 0; i < words.length; i++) { + result.append(words[i]); + + if (i < binary.length()) { + // Add either two spaces (for 1) or one space (for 0) + result.append(binary.charAt(i) == '1' ? " " : " "); + } else if (i < words.length - 1) { + // Add normal space for remaining words + result.append(" "); + } + } + return result.toString(); + } + + @Override + public String decode() { + if (stegoText == null) { + throw new IllegalStateException("No encoded message found"); + } + + StringBuilder binary = new StringBuilder(); + String[] parts = stegoText.split("\\S+"); + + // Skip first empty part if exists + for (int i = 1; i < parts.length; i++) { + if (!parts[i].isEmpty()) { + binary.append(parts[i].length() > 1 ? "1" : "0"); + } + } + + // Convert binary back to text + StringBuilder message = new StringBuilder(); + String binaryStr = binary.toString(); + + for (int i = 0; i + 7 < binaryStr.length(); i += 8) { + String byteStr = binaryStr.substring(i, i + 8); + int charCode = Integer.parseInt(byteStr, 2); + message.append((char) charCode); + } + + return message.toString().trim(); // Add trim() here + } + + @Override + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} \ No newline at end of file diff --git a/src/Classes/Steganography/Video.java b/src/Classes/Steganography/Video.java new file mode 100644 index 0000000..b0fd249 --- /dev/null +++ b/src/Classes/Steganography/Video.java @@ -0,0 +1,247 @@ +package Classes.Steganography; + +import org.bytedeco.ffmpeg.global.avcodec; +import org.bytedeco.javacv.FFmpegFrameGrabber; +import org.bytedeco.javacv.FFmpegFrameRecorder; +import org.bytedeco.javacv.Frame; +import org.bytedeco.javacv.Java2DFrameConverter; +import org.bytedeco.ffmpeg.global.avutil; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * The {@code Video} class provides methods to encode and decode messages within video files using steganography. + * + *

WARNING: This implementation is currently not functional.

+ * + *

This class uses FFmpeg for video processing and Java2D for image manipulation. The message is encoded into the + * video frames by modifying the least significant bit of the pixel values.

+ * + *

Usage:

+ *
+ * {@code
+ * File inputFile = new File("path/to/input/video.mp4");
+ * File outputFile = new File("path/to/output/video.mp4");
+ * Video videoSteganography = new Video(inputFile, outputFile);
+ * 
+ * // Encode a message
+ * videoSteganography.encode("Your secret message");
+ * 
+ * // Decode the message
+ * String decodedMessage = videoSteganography.decode();
+ * }
+ * 
+ * + *

Note: Ensure that the input and output files are valid and accessible.

+ * + * @see Steganography + */ +public class Video extends Steganography { + private File inputFile; + private File outputFile; + private static final String END_MARKER = "###END###"; + private static final int PIXELS_PER_FRAME = 1000; + + static { + avutil.av_log_set_level(avutil.AV_LOG_QUIET); // Suppress FFmpeg logs + } + + // WARNING: This implementation is currently not functional. + public Video(File inputFile, File outputFile) throws IOException { + if (!inputFile.exists()) { + throw new IOException("Input file does not exist: " + inputFile.getAbsolutePath()); + } + this.inputFile = inputFile; + this.outputFile = outputFile; + + File outputDir = outputFile.getParentFile(); + if (outputDir != null && !outputDir.exists()) { + if (!outputDir.mkdirs()) { + throw new IOException("Failed to create output directory: " + outputDir.getAbsolutePath()); + } + } + } + + @Override + public void encode(String message) throws IOException { + if (message == null || message.isEmpty()) { + throw new IllegalArgumentException("Message cannot be null or empty"); + } + + message = message + END_MARKER; // Append end marker to the message + byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8); + System.out.println("[DEBUG] Encoding message: '" + message + "'"); + System.out.println("[DEBUG] Message bytes length: " + messageBytes.length); + + try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputFile); + Java2DFrameConverter converter = new Java2DFrameConverter()) { + + grabber.start(); + FFmpegFrameRecorder recorder = createRecorder(grabber); + + try { + recorder.start(); + int totalBits = messageBytes.length * 8; // Total bits to encode + int encodedBits = 0; + Frame frame; + int frameCount = 0; + boolean messageDone = false; + + while ((frame = grabber.grab()) != null) { + if (frame.image != null) { + BufferedImage img = converter.convert(frame); + + if (!messageDone && encodedBits < totalBits) { + for (int i = 0; i < PIXELS_PER_FRAME && encodedBits < totalBits; i++) { + int x = (i % (img.getWidth() - 1)) + 1; + int y = (i / (img.getWidth() - 1)) + 1; + + int byteIndex = encodedBits / 8; + int bitIndex = 7 - (encodedBits % 8); + int bit = (messageBytes[byteIndex] >> bitIndex) & 1; + + embedBitInPixel(img, x, y, 0, bit); // Embed bit in pixel + encodedBits++; + } + + if (encodedBits >= totalBits) { + messageDone = true; + } + } + + frame = converter.convert(img); + recorder.record(frame); + frameCount++; + + if (frameCount % 30 == 0) { + System.out.println("[DEBUG] Processed frames: " + frameCount + ", Encoded bits: " + encodedBits + "/" + totalBits); + } + } + } + + System.out.println("[DEBUG] Total frames processed: " + frameCount); + System.out.println("[DEBUG] Total bits encoded: " + encodedBits); + + recorder.stop(); + recorder.release(); + + } catch (Exception e) { + cleanupOnError(recorder); + throw e; + } + } + } + + private FFmpegFrameRecorder createRecorder(FFmpegFrameGrabber grabber) { + FFmpegFrameRecorder recorder = new FFmpegFrameRecorder( + outputFile, + grabber.getImageWidth(), + grabber.getImageHeight() + ); + recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); + recorder.setFormat("mp4"); + recorder.setFrameRate(grabber.getFrameRate()); + recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P); + recorder.setVideoQuality(0); + recorder.setVideoOption("preset", "veryslow"); + recorder.setVideoOption("crf", "18"); + return recorder; + } + + @Override + public String decode() throws IOException { + StringBuilder binaryMessage = new StringBuilder(); + byte[] messageBytes = new byte[1024 * 1024]; + int messageLength = 0; + int bitsRead = 0; + + try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(outputFile); + Java2DFrameConverter converter = new Java2DFrameConverter()) { + + grabber.start(); + System.out.println("[DEBUG] Starting decoding process"); + + Frame frame; + int frameCount = 0; + byte currentByte = 0; + int bitCount = 0; + while ((frame = grabber.grab()) != null) { + if (frame.image != null) { + BufferedImage img = converter.convert(frame); + + for (int i = 0; i < PIXELS_PER_FRAME; i++) { + int x = (i % (img.getWidth() - 1)) + 1; + int y = (i / (img.getWidth() - 1)) + 1; + + int bit = extractBitFromPixel(img, x, y, 0); // Extract bit from pixel + currentByte = (byte) ((currentByte << 1) | bit); + bitCount++; + bitsRead++; + + if (bitCount == 8) { + messageBytes[messageLength++] = currentByte; + currentByte = 0; + bitCount = 0; + + String currentMessage = new String(messageBytes, 0, messageLength, StandardCharsets.UTF_8); + if (currentMessage.contains(END_MARKER)) { + String result = currentMessage.substring(0, currentMessage.indexOf(END_MARKER)); + System.out.println("[DEBUG] Found end marker at length: " + messageLength); + return result; + } + } + } + + frameCount++; + if (frameCount % 30 == 0) { + System.out.println("[DEBUG] Processed frames: " + frameCount + ", Bits read: " + bitsRead); + } + } + } + + System.out.println("[DEBUG] Total frames processed: " + frameCount); + System.out.println("[DEBUG] Total bits read: " + bitsRead); + System.out.println("[DEBUG] Message length: " + messageLength); + + throw new IOException("Failed to decode message: End marker not found or message corrupted"); + } + } + + private void embedBitInPixel(BufferedImage img, int x, int y, int channel, int bit) { + int rgb = img.getRGB(x, y); + int[] channels = { + (rgb >> 16) & 0xFF, + (rgb >> 8) & 0xFF, + rgb & 0xFF + }; + + channels[channel] = (channels[channel] & ~1) | bit; // Embed bit in the specified channel + + rgb = (channels[0] << 16) | (channels[1] << 8) | channels[2]; + img.setRGB(x, y, rgb); + } + + private int extractBitFromPixel(BufferedImage img, int x, int y, int channel) { + int rgb = img.getRGB(x, y); + int[] channels = { + (rgb >> 16) & 0xFF, + (rgb >> 8) & 0xFF, + rgb & 0xFF + }; + return channels[channel] & 1; // Extract bit from the specified channel + } + + private void cleanupOnError(FFmpegFrameRecorder recorder) { + try { + recorder.stop(); + recorder.release(); + } catch (Exception ex) { + } + if (outputFile.exists()) { + outputFile.delete(); // Delete output file on error + } + } +} \ No newline at end of file diff --git a/src/Classes/VigenereAlgo.java b/src/Classes/VigenereAlgo.java new file mode 100644 index 0000000..673ba81 --- /dev/null +++ b/src/Classes/VigenereAlgo.java @@ -0,0 +1,146 @@ +package Classes; + + +public class VigenereAlgo { + + + String plainText = "TEST"; + + String key = "LONGKEY"; + // Call encryption function and display the result + String encryptedText = encrypt(plainText, key); + + + // Call decryption function and display the result + String decryptedText = decrypt(encryptedText, key); + + + // Validate that the key does not contain numbers + private static boolean isValidKey(String key) { + for (char c : key.toCharArray()) { + if (Character.isDigit(c)) { + return false; // Reject if the key contains any digit + } + } + return true; // Key is valid if it contains no digits + } + + // Encrypt the plaintext using the Vigenere cipher algorithm + public static String encrypt(String plainText, String key) { + if (!isValidKey(key)) { + throw new IllegalArgumentException("Key must contain only alphabetic characters."); + } + + StringBuilder encryptedText = new StringBuilder(); + + // Clean the key to contain only alphabetical characters + key = cleanKey(key); + // Generate a full-length key that matches the length of the plaintext + key = generateFullKey(plainText, key); + + for (int i = 0, keyIndex = 0; i < plainText.length(); i++) { + char pi = plainText.charAt(i); + + // Only encrypt alphabetic characters + if (Character.isLetter(pi)) { + boolean isUpperCase = Character.isUpperCase(pi); + char normalizedPi = Character.toLowerCase(pi); // the calculations are based on lowerCase alphabet + char ki = key.charAt(keyIndex++); + + + // encrypt formula + // normalizedPi - a : converts the character to an index between 0 and 25 (ex : normalizedPi = 'c' (ASCII 99), then 'c' - 'a' = 99-97 = 2) + // ki - 'a' similarly normalizes the key character to the same 0–25 range. + + // normalizedPi - 'a' and ki - 'a' are added together to combine the plaintext and key in the normalized alphabet range. + + // % 26 ensures the result wraps around if it exceeds 25 (e.g., for 'z'). + // Example: If normalizedPi = 'z' (25) and ki = 'y' (24), then: + // (25 + 24) % 26 = 49 % 26 = 23 (which corresponds to 'x'). + + + // + 'a' converts the normalized result (0–25) back into a valid ASCII character (alphabetical range). + // Example: If the result is 2, adding 'a' (97) gives 99, which corresponds to 'c' in ASCII. + // (char) : Casts the result back to a character + char ci = (char) (((normalizedPi - 'a' + ki - 'a') % 26) + 'a'); + // + 'a' converts the normalized result (0 - 25) back into a valid ASCII character (97-122) + encryptedText.append(isUpperCase ? Character.toUpperCase(ci) : ci); + } else { + // Keep non-alphabetic characters unchanged + encryptedText.append(pi); + } + } + + return encryptedText.toString(); + } + + // Decrypt the ciphertext using the Vigenere cipher algorithm + public static String decrypt(String encryptedText, String key) { + if (!isValidKey(key)) { + throw new IllegalArgumentException("Key must contain only alphabetic characters."); + } + + StringBuilder decryptedText = new StringBuilder(); + + // Clean the key to contain only alphabetical characters + key = cleanKey(key); + // Generate a full-length key that matches the length of the ciphertext + key = generateFullKey(encryptedText, key); + + for (int i = 0, keyIndex = 0; i < encryptedText.length(); i++) { + char ci = encryptedText.charAt(i); + + // Only decrypt alphabetic characters + if (Character.isLetter(ci)) { + boolean isUpperCase = Character.isUpperCase(ci); + char normalizedCi = Character.toLowerCase(ci); + char ki = key.charAt(keyIndex++); + + // Decryption formula: Pi = (Ci - Ki + 26) mod 26 + char pi = (char) (((normalizedCi - ki + 26) % 26) + 'a'); + decryptedText.append(isUpperCase ? Character.toUpperCase(pi) : pi); + } else { + // Keep non-alphabetic characters unchanged + decryptedText.append(ci); + } + } + + return decryptedText.toString(); + } + + + // Generate a full-length key matching the text length, ignoring non-alphabetic characters + public static String generateFullKey(String text, String key) { + StringBuilder fullKey = new StringBuilder(); + int keyLength = key.length(); + int keyIndex = 0; + + // go threw the text length + for (int i = 0; i < text.length(); i++) { + char currentChar = text.charAt(i); + if (Character.isLetter(currentChar)) { + // if keyLength > KeyIndex : troncate the keyLength to keyIndex + fullKey.append(key.charAt(keyIndex % keyLength)); + keyIndex++; + } + } + + return fullKey.toString(); + } + + + + + // Remove non-alphabetical characters from the key + private static String cleanKey(String key) { + StringBuilder cleanedKey = new StringBuilder(); + + for (char c : key.toCharArray()) { + if (Character.isLetter(c)) { + cleanedKey.append(Character.toLowerCase(c)); + } + } + + return cleanedKey.toString(); + } +} diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..c45f744 --- /dev/null +++ b/src/META-INF/MANIFEST.MF @@ -0,0 +1,4 @@ +Manifest-Version: 1.0 +Main-Class: Classes.Menu +Class-Path: src + diff --git a/src/Main.java b/src/Main.java index d5238c9..265783a 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,5 +1,7 @@ +import Classes.Menu; + public class Main { public static void main(String[] args) { - System.out.println("Hello, World!"); + Menu.main(null); } } \ No newline at end of file diff --git a/src/Tests/DatabasesManagerTest.java b/src/Tests/DatabasesManagerTest.java new file mode 100644 index 0000000..e69de29 diff --git a/src/Tests/EnigmaTest.java b/src/Tests/EnigmaTest.java new file mode 100644 index 0000000..8066df6 --- /dev/null +++ b/src/Tests/EnigmaTest.java @@ -0,0 +1,155 @@ +package Tests; + +import Classes.Enigma.Enigma; +import Classes.Enigma.Plugboard; +import Classes.Enigma.Reflector; +import Classes.Enigma.Rotors; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class EnigmaTest { + + @Test + public void testPlugboardConnection() { + Plugboard plugboard = new Plugboard(); + // Test basic connection + plugboard.connect('A', 'B'); + assertEquals('B', plugboard.swap('A')); + assertEquals('A', plugboard.swap('B')); + assertEquals('C', plugboard.swap('C')); // Unconnected letter should return itself + } + + @Test(expected = IllegalArgumentException.class) + public void testPlugboardDoubleConnection() { + Plugboard plugboard = new Plugboard(); + plugboard.connect('A', 'B'); + plugboard.connect('A', 'C'); // Should throw exception + } + + @Test + public void testRotorStepping() { + Rotors rotors = new Rotors(); + char[] initialPositions = {'A', 'A', 'A'}; + rotors.setPositions(initialPositions); + + // Test basic rotation + char result = rotors.rotate('A'); + assertNotEquals('A', result); + + // Test multiple characters to ensure proper stepping + String input = "HELLO"; + StringBuilder output = new StringBuilder(); + for (char c : input.toCharArray()) { + output.append(rotors.rotate(c)); + } + assertNotEquals(input, output.toString()); + } + + @Test + public void testCompleteEnigmaEncryption() { + // Create Enigma machine components + Rotors rotors = new Rotors(); + Reflector reflector = new Reflector(); + Plugboard plugboard = new Plugboard(); + + // Set up plugboard connections + plugboard.connect('A', 'M'); + plugboard.connect('B', 'N'); + plugboard.connect('C', 'O'); + + // Create Enigma machine + Enigma enigma = new Enigma(rotors, reflector, plugboard); + + // Test encryption + String message = "HELLO WORLD"; + String encrypted = enigma.encrypt(message); + + // Test that encryption changes the message + assertNotEquals(message, encrypted); + + // Test that decryption returns the original message + String decrypted = enigma.decrypt(encrypted); + assertEquals(message, decrypted); + } + + @Test + public void testRotorPosition() { + Rotors rotors = new Rotors(); + char[] positions = {'B', 'C', 'D'}; + rotors.setPositions(positions); + + // Encrypt same letter multiple times to test different rotor positions + String input = "AAAAA"; + StringBuilder output = new StringBuilder(); + for (char c : input.toCharArray()) { + output.append(rotors.rotate(c)); + } + + // Each 'A' should encrypt to a different letter due to rotor movement + String result = output.toString(); + for (int i = 1; i < result.length(); i++) { + assertNotEquals(result.charAt(i-1), result.charAt(i)); + } + } + + @Test + public void testReflector() { + Reflector reflector = new Reflector(); + char input = 'A'; + char reflected = reflector.reflect(input); + + // Test that reflection is consistent + assertEquals(reflected, reflector.reflect(input)); + + // Test that reflection is reciprocal + assertEquals(input, reflector.reflect(reflected)); + } + + @Test + public void testLongMessage() { + Enigma enigma = new Enigma(); + // Set initial rotor positions + char[] initialPositions = {'A', 'A', 'A'}; + enigma.getRotors().setPositions(initialPositions); + + String longMessage = "THIS IS A VERY LONG MESSAGE THAT SHOULD TEST THE FULL FUNCTIONALITY " + + "OF THE ENIGMA MACHINE INCLUDING MULTIPLE ROTOR ROTATIONS AND STEPPING"; + + String encrypted = enigma.encrypt(longMessage); + String decrypted = enigma.decrypt(encrypted); + + // Test that encryption changes the message + assertNotEquals(longMessage, encrypted); + + // Test that decryption recovers the original message + assertEquals(longMessage, decrypted); + } + + @Test + public void testNonAlphabeticCharacters() { + Enigma enigma = new Enigma(); + String message = "HELLO, WORLD! 123"; + String encrypted = enigma.encrypt(message); + String decrypted = enigma.decrypt(encrypted); + + // Test that non-alphabetic characters remain unchanged + assertTrue(encrypted.contains(",")); + assertTrue(encrypted.contains("!")); + assertTrue(encrypted.contains("123")); + + // Test that the original message is recovered + assertEquals(message, decrypted); + } + + @Test + public void testCaseSensitivity() { + Enigma enigma = new Enigma(); + String message = "Hello World"; + String encrypted = enigma.encrypt(message); + String decrypted = enigma.decrypt(encrypted); + + // Test that the case of the original message is preserved + assertEquals(message, decrypted); + } +} \ No newline at end of file diff --git a/src/Tests/HashTest.java b/src/Tests/HashTest.java new file mode 100644 index 0000000..5c7af4d --- /dev/null +++ b/src/Tests/HashTest.java @@ -0,0 +1,29 @@ +package Tests; + +import Classes.Hash; +import org.junit.Test; +import static org.junit.Assert.*; + +public class HashTest { + + private class DummyHash extends Hash { + @Override + public String calculateHash(String input) { + return "dummyhash"; + } + } + + @Test + public void testPrintHash() { + DummyHash dummyHash = new DummyHash(); + dummyHash.printHash("test"); + } + + @Test + public void testCalculateHash() { + DummyHash dummyHash = new DummyHash(); + String result = dummyHash.calculateHash("test"); + assertEquals("dummyhash", result); + } +} + diff --git a/src/Tests/LfsrTest.java b/src/Tests/LfsrTest.java new file mode 100644 index 0000000..86a58a8 --- /dev/null +++ b/src/Tests/LfsrTest.java @@ -0,0 +1,102 @@ +package Tests; + +import Classes.Lfsr; +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.*; + +public class LfsrTest { + private Lfsr lfsr; + + @Before + public void setUp() { + lfsr = new Lfsr((char) 0, (char) 0); + } + + @Test + public void testParity() { + // Test the parity (XOR of all bits) + assertEquals(0, Lfsr.parity(0)); // 0000 -> parity 0 + assertEquals(1, Lfsr.parity(1)); // 0001 -> parity 1 + assertEquals(0, Lfsr.parity(3)); // 0011 -> parity 0 (1 XOR 1 = 0) + assertEquals(1, Lfsr.parity(7)); // 0111 -> parity 1 (1 XOR 1 XOR 1 = 1) + assertEquals(0, Lfsr.parity(15)); // 1111 -> parity 0 (1 XOR 1 XOR 1 XOR 1 = 0) + } + + @Test + public void testConvertor() { + assertEquals("0000000000000000", Lfsr.convertor(0)); + assertEquals("0000000000000001", Lfsr.convertor(1)); + assertEquals("0000000000001111", Lfsr.convertor(15)); + } + + @Test + public void testFibonacciBasic() { + lfsr = new Lfsr((char) 1, (char) 1); // Simple initial state + String result = lfsr.fibonacci(4); + assertNotNull(result); + assertEquals(4, result.length()); + } + + @Test + public void testGaloisBasic() { + lfsr = new Lfsr((char) 1, (char) 1); // Simple initial state + String result = lfsr.galois(4); + assertNotNull(result); + assertEquals(4, result.length()); + } + + @Test + public void testLfsrBasicOperation() { + // Test with simple values + String result = lfsr.lfsr("1", 4, "1", "galois"); + assertNotNull(result); + assertEquals(4, result.length()); + } + + @Test + public void testDifferentModes() { + String galoisResult = lfsr.lfsr("1", 4, "1", "galois"); + String fibonacciResult = lfsr.lfsr("1", 4, "1", "fibonacci"); + // The results should be different for the two modes + assertNotEquals(galoisResult, fibonacciResult); + } + + @Test + public void testOutputLength() { + String result4 = lfsr.lfsr("1", 4, "1", "galois"); + String result8 = lfsr.lfsr("1", 8, "1", "galois"); + assertEquals(4, result4.length()); + assertEquals(8, result8.length()); + } + + @Test + public void testConsistency() { + // The same input should give the same output + String result1 = lfsr.lfsr("1", 4, "1", "galois"); + String result2 = lfsr.lfsr("1", 4, "1", "galois"); + assertEquals(result1, result2); + } + + @Test + public void testModeInsensitiveCase() { + String result1 = lfsr.lfsr("1", 4, "1", "GALOIS"); + String result2 = lfsr.lfsr("1", 4, "1", "galois"); + assertEquals(result1, result2); + } + + @Test + public void testWithZeroTaps() { + String result = lfsr.lfsr("0", 4, "1", "galois"); + assertNotNull(result); + assertEquals(4, result.length()); + } + + @Test + public void testWithZeroSeed() { + String result = lfsr.lfsr("1", 4, "0", "galois"); + assertNotNull(result); + assertEquals(4, result.length()); + assertEquals("0000", result); // With seed 0, the output should be all 0s + } +} \ No newline at end of file diff --git a/src/Tests/MD5Test.java b/src/Tests/MD5Test.java new file mode 100644 index 0000000..d5397ff --- /dev/null +++ b/src/Tests/MD5Test.java @@ -0,0 +1,82 @@ +package Tests; + +import Classes.MD5; +import org.junit.Before; +import org.junit.Test; +import org.junit.Assert; + +public class MD5Test { + + private MD5 md5; + + @Before + public void setUp() { + md5 = new MD5(); + } + @Test + public void testEmptyString() { + + String result = md5.calculateHash(""); + Assert.assertEquals("d41d8cd98f00b204e9800998ecf8427e", result); + } + + @Test + public void testSimpleString() { + String result = md5.calculateHash("test"); + Assert.assertEquals("098f6bcd4621d373cade4e832627b4f6", result); + } + + @Test + public void testLongString() { + String result = md5.calculateHash("The quick brown fox jumps over the lazy dog"); + Assert.assertEquals("9e107d9d372bb6826bd81d3542a419d6", result); + } + + @Test + public void testSpecialCharacters() { + String result = md5.calculateHash("!@#$%^&*()_+"); + Assert.assertEquals("04dde9f462255fe14b5160bbf2acffe8", result); + } + + @Test + public void testUnicodeCharacters() { + String result = md5.calculateHash("héllo wörld"); + Assert.assertEquals("ed0c22cc110ede12327851863c078138", result); + } + + @Test + public void testConsistency() { + String input = "test123"; + String firstHash = md5.calculateHash(input); + String secondHash = md5.calculateHash(input); + Assert.assertEquals("The same input should produce the same hash", firstHash, secondHash); + } + + @Test(expected = RuntimeException.class) + public void testNullInput() { + md5.calculateHash(null); + } + + @Test + public void testCaseSensitivity() { + String lowerCase = md5.calculateHash("hello"); + String upperCase = md5.calculateHash("HELLO"); + Assert.assertNotEquals("Hashes of different cases should be different", + lowerCase, upperCase); + } + + @Test + public void testHashLength() { + String result = md5.calculateHash("test"); + Assert.assertEquals("The length of the md5 hash should be 32 characters", + 32, result.length()); + } + + @Test + public void testWhitespaceHandling() { + String withSpaces = md5.calculateHash("test test"); + String withoutSpaces = md5.calculateHash("testtest"); + Assert.assertNotEquals("Spaces should affect the hash", + withSpaces, withoutSpaces); + } +} \ No newline at end of file diff --git a/src/Tests/PasswordUtilsTest.java b/src/Tests/PasswordUtilsTest.java new file mode 100644 index 0000000..0d59604 --- /dev/null +++ b/src/Tests/PasswordUtilsTest.java @@ -0,0 +1,20 @@ +package Tests; + +import Classes.PasswordUtils; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class PasswordUtilsTest { + + @Test + public void testGenerateRandomPassword() { + String password1 = PasswordUtils.generateRandomPassword(12); + String password2 = PasswordUtils.generateRandomPassword(12); + + assertNotEquals(password1, password2); + + assertEquals(12, password1.length()); + assertEquals(12, password2.length()); + } +} diff --git a/src/Tests/PolybSquareEncryterTest.java b/src/Tests/PolybSquareEncryterTest.java new file mode 100644 index 0000000..084c2e3 --- /dev/null +++ b/src/Tests/PolybSquareEncryterTest.java @@ -0,0 +1,86 @@ +package Tests; + +import Classes.PolybSquareEncrypter; +import Classes.PolybSquareLayout; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class PolybSquareEncryterTest +{ + private PolybSquareEncrypter horizontalLayoutPolybSquareEncrypter; + + // Horizontal layout + + // Encryption + @Before + public void setUp() { + this.horizontalLayoutPolybSquareEncrypter = new PolybSquareEncrypter(PolybSquareLayout.HORIZONTAL); + } + + @Test + public void testPolybSquareEncryterEncryption() { + String plainText = ("hello"); + String expectedEncryptedText = "3251232353"; + String encryptionResult = this.horizontalLayoutPolybSquareEncrypter.encrypt(plainText); + + // Validation + assertEquals(expectedEncryptedText, encryptionResult); + } + + @Test + public void testPolybSquareEncryterEncryptionEmptyString() { + String plainText = (""); + String expectedEncryptedText = ""; + String encryptionResult = this.horizontalLayoutPolybSquareEncrypter.encrypt(plainText); + + // Validation + assertEquals(expectedEncryptedText, encryptionResult); + } + + @Test + public void testPolybSquareEncryterEncryptionW() { + String plainText = ("w"); + String expectedEncryptedText = "2525"; + String encryptionResult = this.horizontalLayoutPolybSquareEncrypter.encrypt(plainText); + + // Validation + assertEquals(expectedEncryptedText, encryptionResult); + } + + // Decryption + @Test + public void testPolybSquareEncryterDecryption() { + String encryptedText = ("3251232353"); + String expectedPlainText = "hello"; + String decryptionResult = this.horizontalLayoutPolybSquareEncrypter.decrypt(encryptedText); + + // Validation + assertEquals(expectedPlainText, decryptionResult); + } + + @Test + public void testPolybSquareEncryterDecryptionEmptyString() { + String encryptedText = (""); + String expectedPlainText = ""; + String decryptionResult = this.horizontalLayoutPolybSquareEncrypter.decrypt(encryptedText); + + // Validation + assertEquals(expectedPlainText, decryptionResult); + } + + @Test + public void testPolybSquareEncryterDecryptionW() { + String encryptedText = ("2525"); + String expectedPlainText = "vv"; + String decryptionResult = this.horizontalLayoutPolybSquareEncrypter.decrypt(encryptedText); + + // Validation + assertEquals(expectedPlainText, decryptionResult); + } + + // TODO Vertical Layout + // TODO ClockWise spiral layout + // TODO CounterClockWise spiral layout +} diff --git a/src/Tests/RC4Test.java b/src/Tests/RC4Test.java new file mode 100644 index 0000000..558e41f --- /dev/null +++ b/src/Tests/RC4Test.java @@ -0,0 +1,128 @@ +package Tests; + +import org.junit.Test; +import org.junit.Before; +import org.junit.Assert; +import Classes.RC4; + +public class RC4Test { + private RC4 rc4; + private static final String KEY = "1234"; + private static final String CLEAR_TEXT = "Hello World"; + private static final String ENCRYPTED_TEXT = "01001101 00101100 00011101 11000011 10011100 11001100 01010000 01100010 01111101 01110111 01100100"; + + @Before + public void setUp() { + rc4 = new RC4(); + rc4.key = KEY; + rc4.init(KEY); + } + + @Test + public void testInit() { + RC4 testRc4 = new RC4(); + testRc4.init(KEY); + Assert.assertNotNull("Initialization should not produce a null array", testRc4); + } + + @Test(expected = IllegalArgumentException.class) + public void testInitWithNullKey() { + rc4.init(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testInitWithEmptyKey() { + rc4.init(""); + } + + @Test + public void testEncrypt() { + String encrypted = rc4.encrypt(CLEAR_TEXT); + Assert.assertEquals("The encrypted text does not match the expected result", + ENCRYPTED_TEXT, encrypted); + } + + @Test + public void testEncryptEmptyString() { + try { + rc4.encrypt(""); + Assert.fail("An empty string should throw an exception"); + } catch (IllegalArgumentException e) { + Assert.assertTrue(true); + } + } + + @Test(expected = IllegalArgumentException.class) + public void testEncryptNull() { + rc4.encrypt(null); + } + + @Test + public void testDecrypt() { + String decrypted = rc4.decrypt(ENCRYPTED_TEXT); + Assert.assertEquals("The decrypted text does not match the original text", + CLEAR_TEXT, decrypted); + } + + @Test + public void testDecryptEmptyString() { + try { + rc4.decrypt(""); + Assert.fail("An empty string should throw an exception"); + } catch (IllegalArgumentException e) { + Assert.assertTrue(true); + } + } + + @Test(expected = IllegalArgumentException.class) + public void testDecryptNull() { + rc4.decrypt(null); + } + + @Test + public void testFullCycle() { + String encrypted = rc4.encrypt(CLEAR_TEXT); + rc4.init(KEY); // Reinitialization needed for decryption + String decrypted = rc4.decrypt(encrypted); + Assert.assertEquals("The full encryption/decryption cycle failed", + CLEAR_TEXT, decrypted); + } + + @Test + public void testDifferentKeys() { + String encrypted1 = rc4.encrypt(CLEAR_TEXT); + rc4.init("5678"); // Using a different key + String encrypted2 = rc4.encrypt(CLEAR_TEXT); + Assert.assertNotEquals("Different keys should produce different encryptions", + encrypted1, encrypted2); + } + + @Test + public void testConsistentEncryption() { + String encrypted1 = rc4.encrypt(CLEAR_TEXT); + rc4.init(KEY); + String encrypted2 = rc4.encrypt(CLEAR_TEXT); + Assert.assertEquals("The same text with the same key should produce the same encryption", + encrypted1, encrypted2); + } + + @Test + public void testLongText() { + String longText = "This is a longer text that should also work properly with RC4 encryption!"; + String encrypted = rc4.encrypt(longText); + rc4.init(KEY); + String decrypted = rc4.decrypt(encrypted); + Assert.assertEquals("Encryption/decryption of a long text failed", + longText, decrypted); + } + + @Test + public void testSpecialCharacters() { + String specialText = "Hello! @#$%^&*()_+ World"; + String encrypted = rc4.encrypt(specialText); + rc4.init(KEY); + String decrypted = rc4.decrypt(encrypted); + Assert.assertEquals("Encryption/decryption with special characters failed", + specialText, decrypted); + } +} \ No newline at end of file diff --git a/src/Tests/ROTXTest.java b/src/Tests/ROTXTest.java new file mode 100644 index 0000000..87e1401 --- /dev/null +++ b/src/Tests/ROTXTest.java @@ -0,0 +1,108 @@ +package Tests; + +import org.junit.Test; +import static org.junit.Assert.*; +import Classes.ROTX; + +public class ROTXTest { + + // Base test with classic rotation + @Test + public void testRotationPositive() { + String input = "abc"; + int shift = 3; + String expected = "def"; // "abc" with ROT(3) becomes "def" + String result = ROTX.encryptROT(input, shift); + assertEquals("Le texte n'est pas correctement transformé", expected, result); + } + + // Test with a null function + @Test + public void testRotationZero() { + String input = "abc"; + int shift = 0; + String expected = "abc"; // ROT(0) don't modify the text + String result = ROTX.encryptROT(input, shift); + assertEquals("La rotation nulle n'a pas fonctionné comme prévu", expected, result); + } + + // Test with negative rotation + @Test + public void testRotationNegative() { + String input = "abc"; + int shift = -3; + String expected = "xyz"; // "abc" with ROT(-3) becomes "xyz" + String result = ROTX.encryptROT(input, shift); + assertEquals("La rotation négative ne donne pas le bon résultat", expected, result); + } + + // Test with non-alphabetical characters (spaces and symbols) + @Test + public void testNonAlphabeticCharacters() { + String input = "abc 123!"; + int shift = 3; + String expected = "def 123!"; // Non-alphabetical characters must not be affected + String result = ROTX.encryptROT(input, shift); + assertEquals("Les caractères non alphabétiques ne sont pas correctement gérés", expected, result); + } + + // Test on an empty text + @Test + public void testEmptyString() { + String input = ""; + int shift = 5; + String expected = ""; // La chaîne vide ne doit pas être modifiée + String result = ROTX.encryptROT(input, shift); + assertEquals("La chaîne vide n'a pas été gérée correctement", expected, result); + } + + // Test de décryptage avec une rotation positive + @Test + public void testDecryptionPositive() { + String input = "def"; + int shift = 3; + String expected = "abc"; // "def" avec ROT(-3) devient "abc" + String result = ROTX.decryptROT(input, shift); + assertEquals("Le texte n'a pas été correctement décrypté", expected, result); + } + + // Test de décryptage avec une rotation négative + @Test + public void testDecryptionNegative() { + String input = "xyz"; + int shift = -3; + String expected = "abc"; // "xyz" avec ROT(3) devient "abc" + String result = ROTX.decryptROT(input, shift); + assertEquals("Le texte n'a pas été correctement décrypté", expected, result); + } + + // Test de décryptage avec une rotation nulle + @Test + public void testDecryptionZero() { + String input = "abc"; + int shift = 0; + String expected = "abc"; // ROT(0) ne modifie pas le texte + String result = ROTX.decryptROT(input, shift); + assertEquals("La décryptation nulle n'a pas fonctionné comme prévu", expected, result); + } + + // Test de décryptage avec des caractères non alphabétiques + @Test + public void testDecryptionNonAlphabeticCharacters() { + String input = "def 123!"; + int shift = 3; + String expected = "abc 123!"; // Les caractères non alphabétiques ne doivent pas être affectés + String result = ROTX.decryptROT(input, shift); + assertEquals("Les caractères non alphabétiques ne sont pas correctement décryptés", expected, result); + } + + // Test de décryptage avec une chaîne vide + @Test + public void testDecryptionEmptyString() { + String input = ""; + int shift = 5; + String expected = ""; // La chaîne vide ne doit pas être modifiée + String result = ROTX.decryptROT(input, shift); + assertEquals("La chaîne vide n'a pas été décryptée correctement", expected, result); + } +} diff --git a/src/Tests/SHA256Test.java b/src/Tests/SHA256Test.java new file mode 100644 index 0000000..2c2bdaa --- /dev/null +++ b/src/Tests/SHA256Test.java @@ -0,0 +1,28 @@ +package Tests; + +import Classes.Sha256; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class SHA256Test { + @Test + public void testCalculateHash() { + Sha256 sha256 = new Sha256(); + String input = "test"; + String expectedHash = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"; + + String result = sha256.calculateHash(input); + assertEquals(expectedHash, result); + } + + @Test + public void testEmptyInputHash() { + Sha256 sha256 = new Sha256(); + String input = ""; + String expectedHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + + String result = sha256.calculateHash(input); + assertEquals(expectedHash, result); + } +} diff --git a/src/Tests/SiteManagerTest.java b/src/Tests/SiteManagerTest.java new file mode 100644 index 0000000..7401f95 --- /dev/null +++ b/src/Tests/SiteManagerTest.java @@ -0,0 +1,89 @@ +package Classes; + +import org.junit.*; +import java.io.File; +import java.util.*; + +import static org.junit.Assert.*; + +public class SiteManagerTest { + + private File tempFile; + private SiteManager siteManager; + + @Before + public void setUp() { + tempFile = new File("test_db.json"); + Map encryptionMap = Map.of("RotX", "3"); // Exemple de méthode de chiffrement + siteManager = new SiteManager(tempFile, encryptionMap); + } + + @After + public void tearDown() { + if (tempFile.exists()) { + tempFile.delete(); + } + } + + @Test + public void testAddSite() { + siteManager.addSite("example.com", "user123", "pass123"); + + List> sites = siteManager.loadSites(); + assertEquals(1, sites.size()); + + Map site = sites.get(0); + assertEquals("example.com", site.get("siteName")); + assertEquals("user123", site.get("username")); + assertEquals("pass123", site.get("password")); + } + + @Test + public void testModifySite() { + siteManager.addSite("example.com", "user123", "pass123"); + siteManager.modifySite("example.com", "newUser", "newPass"); + + List> sites = siteManager.loadSites(); + assertEquals(1, sites.size()); + + Map site = sites.get(0); + assertEquals("example.com", site.get("siteName")); + assertEquals("newUser", site.get("username")); + assertEquals("newPass", site.get("password")); + } + + @Test + public void testDeleteSite() { + siteManager.addSite("example.com", "user123", "pass123"); + siteManager.deleteSite("example.com"); + + List> sites = siteManager.loadSites(); + assertTrue(sites.isEmpty()); + } + + @Test + public void testLoadSites() { + siteManager.addSite("example.com", "user123", "pass123"); + siteManager.addSite("test.com", "testUser", "testPass"); + + List> sites = siteManager.loadSites(); + assertEquals(2, sites.size()); + + Map site1 = sites.get(0); + Map site2 = sites.get(1); + + assertEquals("example.com", site1.get("siteName")); + assertEquals("user123", site1.get("username")); + assertEquals("pass123", site1.get("password")); + + assertEquals("test.com", site2.get("siteName")); + assertEquals("testUser", site2.get("username")); + assertEquals("testPass", site2.get("password")); + } + + @Test + public void testDisplaySites() { + siteManager.addSite("example.com", "user123", "pass123"); + siteManager.addSite("test.com", "testUser", "testPass"); + } +} diff --git a/src/Tests/SteganographyImageTest.java b/src/Tests/SteganographyImageTest.java new file mode 100644 index 0000000..c0434b9 --- /dev/null +++ b/src/Tests/SteganographyImageTest.java @@ -0,0 +1,51 @@ +package Tests; + +import Classes.Steganography.Image; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +public class SteganographyImageTest { + + private static final String IMAGE_PATH = "src/Tests/assets/image.png"; + private static final String IMAGE_PATH_SMALLEST_PNG = "src/Tests/assets/world_smallest.png"; + private static final String IMAGE_ENCODED_PATH = "src/Tests/assets/image_encoded.png"; + + @Test + public void testEncodeDecode() throws IOException { + Image image = new Image(IMAGE_PATH); + String message = "Hello"; + image.encode(message); + String decodedMessage = image.decode(); + assertEquals(message, decodedMessage); + } + + @Test + public void testDecodeImage() { + Image image = new Image(IMAGE_ENCODED_PATH); + String message = image.decode(); + assertEquals("Hello", message); + } + + @Test + public void testEncodeMessageTooLarge() { + Image image = new Image(IMAGE_PATH_SMALLEST_PNG); + String largeMessage = "This message is too large for the image to hold."; + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + image.encode(largeMessage); + }); + + assertEquals("Message too large for this image", exception.getMessage()); + } + + @Test + public void testDecodeWithoutImage() { + Exception exception = assertThrows(NullPointerException.class, () -> { + new Image(null); + }); + assertEquals(null, exception.getMessage()); + } +} diff --git a/src/Tests/SteganographyTextTest.java b/src/Tests/SteganographyTextTest.java new file mode 100644 index 0000000..2e56ed4 --- /dev/null +++ b/src/Tests/SteganographyTextTest.java @@ -0,0 +1,88 @@ +package Tests; + +import Classes.Steganography.Text; +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.*; + +public class SteganographyTextTest { + private Text steganography; + private static final String LOREM_IPSUM = String.join(" ", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus porttitor augue dignissim mauris tincidunt efficitur. Nunc gravida ante vel iaculis interdum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. " + + "Ut a iaculis dolor. Sed sed aliquet justo, sit amet malesuada " + + "ex. Fusce ullamcorper vehicula tristique. Maecenas sit amet nulla viverra, " + + "consectetur erat ut, porta libero. Sed lorem mauris, hendrerit in aliquam rhoncus, " + + "imperdiet sit amet dolor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + + "Suspendisse vel ultrices ipsum. Nam risus velit, sollicitudin eget sodales eget, " + + "convallis non dolor. Suspendisse in mollis diam. Suspendisse vulputate posuere maximus. " + + "Fusce dignissim elit leo, vitae placerat ex semper et. Mauris fermentum ligula at felis semper viverra." + + " Duis a neque in turpis molestie semper. Duis arcu felis, porta vitae mauris sit amet, facilisis faucibus mauris. Phasellus viverra massa felis, non iaculis neque sagittis at. Nunc nisl lacus, lobortis in malesuada id, pharetra at justo. Sed non turpis ac erat iaculis tincidunt. Proin scelerisque pulvinar nulla non pretium. Donec eget neque a sem placerat tincidunt. Fusce ante ipsum, rhoncus pretium sapien ut, pellentesque bibendum nulla. Phasellus ac accumsan magna. Suspendisse egestas, eros ac feugiat maximus, nibh lorem scelerisque tellus, sed iaculis nulla velit at augue. Curabitur sit amet convallis sapien.Cras cursus ante quis eros tincidunt, et convallis elit faucibus. Cras eget laoreet lacus. Donec dui massa, sagittis eget ligula sed, laoreet suscipit nulla. Suspendisse eget posuere mauris, non faucibus justo. Integer dignissim consectetur ultrices. Suspendisse luctus, enim quis condimentum congue, nisl tortor dapibus nibh, id gravida nulla enim eu felis. Duis rutrum est sed bibendum efficitur. Ut elit elit, ornare eget neque et, vulputate vulputate nunc. Mauris consequat rhoncus risus. Sed imperdiet orci sollicitudin, convallis ligula in, gravida mi. Mauris pharetra lorem vitae sagittis condimentum. Aliquam erat volutpat. Donec cursus quis sem sit amet fringilla. Sed in ante justo. Vivamus tristique diam sit amet porta lobortis. Sed purus odio, egestas vel risus eu, laoreet tempus enim. Nullam vel bibendum nisi, eget dapibus felis. Interdum et malesuada fames ac ante ipsum primis in faucibus. Fusce mattis sit amet nunc sed maximus. Mauris tincidunt non sapien eu eleifend. Nam eget felis a quam pulvinar sodales. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque pharetra volutpat tortor, et bibendum libero mattis idSed in varius felis, vitae pellentesque leo. In hac habitasse platea dictumst. Phasellus in ullamcorper quam. Etiam a ultricies ex. Sed nec nisl in ante semper mollis vel vel mi. Quisque non turpis neque. Donec est eros, efficitur ut lectus sed, viverra pulvinar lectus. Fusce a porta leo. Curabitur vitae erat congue, semper nisl quis, cursus leo. Pellentesque blandit ac nunc ac pretiumNulla sit amet vulputate nunc. Donec et turpis eu mauris tincidunt vulputate et nec dui. Vestibulum quis fermentum arcu. Nulla luctus facilisis turpis, et rutrum ligula fermentum sit amet. Aliquam vitae ornare justo. Nullam placerat sed nibh ornare sodales. Sed nec posuere nunc. Aenean blandit gravida dapibus. Cras fermentum justo ac vestibulum vestibulum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed egestas orci ut mi scelerisque ornare. Morbi ex turpis, tristique eget est id, luctus tempor magna. In quam dui, dapibus nec viverra quis, rutrum eget urna. Curabitur iaculis dui non odio laoreet, ac bibendum orci feugiatDonec quis nisi ante. Ut a dolor arcu. Morbi mattis nulla a ligula congue feugiat. Nullam iaculis risus non metus blandit, viverra pellentesque tellus ullamcorper. Sed odio neque, sodales mollis libero in, tincidunt vulputate nulla. Duis non nunc venenatis, posuere lorem in, vulputate odio. Mauris venenatis efficitur libero. Nullam lectus turpis, tempor."); + + @Before + public void setUp() { + steganography = new Text(); + } + + @Test + public void testSetAndGetContent() { + String content = "This is a test content"; + steganography.setContent(content); + assertEquals(content, steganography.getContent()); + } + + @Test(expected = IllegalArgumentException.class) + public void testEncodeWithNullMessage() { + steganography.setContent("Some content"); + steganography.encode(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testEncodeWithEmptyMessage() { + steganography.setContent("Some content"); + steganography.encode(""); + } + + @Test + public void testEncodeAndDecode() { + String message = "Hello"; + steganography.setContent(LOREM_IPSUM); + steganography.encode(message); + String decoded = steganography.decode(); + assertEquals(message, decoded); + } + + @Test(expected = IllegalStateException.class) + public void testDecodeWithoutEncoding() { + steganography.decode(); + } + + @Test + public void testEncodeLongMessage() { + String message = "This is a longer secret message that needs to be hidden"; + steganography.setContent(LOREM_IPSUM); + steganography.encode(message); + String decoded = steganography.decode(); + assertEquals(message, decoded); + } + + @Test + public void testEncodeSpecialCharacters() { + String message = "Test@#$%"; + steganography.setContent(LOREM_IPSUM); + steganography.encode(message); + String decoded = steganography.decode(); + assertEquals(message, decoded); + } + + @Test + public void testEncodeWithMinimalContent() { + String message = "Hi"; + // Using just enough Lorem Ipsum words for "Hi" (needs 16 bits + 1 word) + String minimalContent = "Lorem ipsum dolor sit amet consectetur adipiscing elit " + + "sed do eiusmod tempor incididunt ut labore et dolore magna"; + steganography.setContent(minimalContent); + steganography.encode(message); + String decoded = steganography.decode(); + assertEquals(message, decoded); + } +} \ No newline at end of file diff --git a/src/Tests/VigenereTest.java b/src/Tests/VigenereTest.java new file mode 100644 index 0000000..94c17da --- /dev/null +++ b/src/Tests/VigenereTest.java @@ -0,0 +1,100 @@ +package Tests; + +import Classes.VigenereAlgo; +import org.junit.Test; +import static org.junit.Assert.*; + +public class VigenereTest { + + private final VigenereAlgo vigenere = new VigenereAlgo(); + + @Test + public void testEncrypt_basic() { + String plainText = "HELLO"; + String key = "KEY"; + String expectedCipherText = "RIJVS"; + assertEquals(expectedCipherText, vigenere.encrypt(plainText, key)); + } + + @Test + public void testDecrypt_basic() { + String cipherText = "RIJVS"; + String key = "KEY"; + String expectedPlainText = "HELLO"; + assertEquals(expectedPlainText, vigenere.decrypt(cipherText, key)); + } + + + @Test + public void testEncrypt_withLowercase() { + String plainText = "hello"; + String key = "key"; + String expectedCipherText = "RIJVS"; + assertEquals(expectedCipherText, vigenere.encrypt(plainText.toUpperCase(), key.toUpperCase())); + } + + @Test + public void testDecrypt_withLowercase() { + String cipherText = "rijvs"; + String key = "key"; + String expectedPlainText = "HELLO"; + assertEquals(expectedPlainText, vigenere.decrypt(cipherText.toUpperCase(), key.toUpperCase())); + } + + @Test + public void testEncrypt_emptyString() { + String plainText = ""; + String key = "KEY"; + assertEquals("", vigenere.encrypt(plainText, key)); + } + + @Test + public void testDecrypt_emptyString() { + String cipherText = ""; + String key = "KEY"; + assertEquals("", vigenere.decrypt(cipherText, key)); + } + + @Test + public void testGenerateFullKey() { + String text = "TEST"; + String key = "LONGKEY"; + String expectedKey = "LONG"; // Doit être exactement de la longueur de "TEST" + String result = VigenereAlgo.generateFullKey(text, key); + + assertEquals(expectedKey, result); + } + + @Test + public void testEncrypt_keyLongerThanText() { + String plainText = "TEST"; + String key = "LONGKEY"; + String expectedCipherText = "ESFZ"; + String result = VigenereAlgo.encrypt(plainText, key); + + assertEquals(expectedCipherText, result); + } + + + @Test + public void testDecrypt_keyLongerThanText() { + String cipherText = "ESFZ"; + String key = "LONGKEY"; + String expectedPlainText = "TEST"; + assertEquals(expectedPlainText, vigenere.decrypt(cipherText, key)); + } + + @Test(expected = IllegalArgumentException.class) + public void testEncrypt_nonAlphabeticKey() { + String plainText = "HELLO"; + String key = "K3Y!"; // Non-alphabetic characters in key + vigenere.encrypt(plainText, key); + } + + @Test(expected = IllegalArgumentException.class) + public void testDecrypt_nonAlphabeticKey() { + String cipherText = "RIJVS"; + String key = "K3Y!"; // Non-alphabetic characters in key + vigenere.decrypt(cipherText, key); + } +} diff --git a/src/Tests/assets/image.png b/src/Tests/assets/image.png new file mode 100644 index 0000000..f93549d Binary files /dev/null and b/src/Tests/assets/image.png differ diff --git a/src/Tests/assets/image_encoded.png b/src/Tests/assets/image_encoded.png new file mode 100644 index 0000000..a759e2f Binary files /dev/null and b/src/Tests/assets/image_encoded.png differ diff --git a/src/Tests/assets/world_smallest.png b/src/Tests/assets/world_smallest.png new file mode 100644 index 0000000..252d950 Binary files /dev/null and b/src/Tests/assets/world_smallest.png differ diff --git a/test.json b/test.json new file mode 100644 index 0000000..48f52d0 --- /dev/null +++ b/test.json @@ -0,0 +1,9 @@ +{ + "eufqe": [ + { + "bmeeiadp": "xutosh", + "eufqZmyq": "uzefm", + "geqdzmyq": "xgome" + } + ] +} \ No newline at end of file