From 134cd4c8c83614c48608e926c336e3e5f9a44e06 Mon Sep 17 00:00:00 2001 From: blueShard-dev <63594396+blueShard-dev@users.noreply.github.com> Date: Mon, 4 May 2020 21:23:29 +0200 Subject: [PATCH] Add files via upload --- src/META-INF/MANIFEST.MF | 3 + src/org/blueshard/cryptogx/Config.java | 789 ++++++++ src/org/blueshard/cryptogx/Controller.java | 1581 +++++++++++++++++ src/org/blueshard/cryptogx/EnDecrypt.java | 531 ++++++ src/org/blueshard/cryptogx/Main.java | 269 +++ src/org/blueshard/cryptogx/SecureDelete.java | 196 ++ src/org/blueshard/cryptogx/Utils.java | 21 + .../cryptogx/resources/addSettingsGUI.fxml | 91 + .../blueshard/cryptogx/resources/close.png | Bin 0 -> 14702 bytes .../blueshard/cryptogx/resources/cryptoGX.png | Bin 0 -> 51400 bytes .../cryptogx/resources/exportSettingsGUI.fxml | 28 + .../cryptogx/resources/loadSettingsGUI.fxml | 26 + .../blueshard/cryptogx/resources/loading.gif | Bin 0 -> 9270 bytes .../blueshard/cryptogx/resources/mainGUI.fxml | 103 ++ .../blueshard/cryptogx/resources/minimize.png | Bin 0 -> 14698 bytes 15 files changed, 3638 insertions(+) create mode 100644 src/META-INF/MANIFEST.MF create mode 100644 src/org/blueshard/cryptogx/Config.java create mode 100644 src/org/blueshard/cryptogx/Controller.java create mode 100644 src/org/blueshard/cryptogx/EnDecrypt.java create mode 100644 src/org/blueshard/cryptogx/Main.java create mode 100644 src/org/blueshard/cryptogx/SecureDelete.java create mode 100644 src/org/blueshard/cryptogx/Utils.java create mode 100644 src/org/blueshard/cryptogx/resources/addSettingsGUI.fxml create mode 100644 src/org/blueshard/cryptogx/resources/close.png create mode 100644 src/org/blueshard/cryptogx/resources/cryptoGX.png create mode 100644 src/org/blueshard/cryptogx/resources/exportSettingsGUI.fxml create mode 100644 src/org/blueshard/cryptogx/resources/loadSettingsGUI.fxml create mode 100644 src/org/blueshard/cryptogx/resources/loading.gif create mode 100644 src/org/blueshard/cryptogx/resources/mainGUI.fxml create mode 100644 src/org/blueshard/cryptogx/resources/minimize.png diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..76191f1 --- /dev/null +++ b/src/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: org.blueshard.cryptogx.Main + diff --git a/src/org/blueshard/cryptogx/Config.java b/src/org/blueshard/cryptogx/Config.java new file mode 100644 index 0000000..f11f65e --- /dev/null +++ b/src/org/blueshard/cryptogx/Config.java @@ -0,0 +1,789 @@ +package org.blueshard.cryptogx; + +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.KeyCode; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.stage.*; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.xml.stream.*; +import javax.xml.transform.*; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import java.io.*; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import static org.blueshard.cryptogx.Main.*; + +/** + * <p>Class for the user configuration / settings</p> + */ +public class Config { + + private static double addConfigGUIX, addConfigGUIY; + + private static final HashSet<String> protectedConfigNames = new HashSet<>(Arrays.asList("cryptoGX", "config")); + + /** + * <p>Shows a GUI where the user can save settings, which can load later</p> + * + * @param rootWindow from which this GUI will get called + * @param userSetting + * @throws IOException + */ + public static void addSettingGUI(Window rootWindow, Map<String, String> userSetting) throws IOException { + Map<String, String> newSettingItems = new HashMap<>(); + + Stage rootStage = new Stage(); + rootStage.initOwner(rootWindow); + Parent addSettingsRoot = FXMLLoader.load(Config.class.getResource("resources/addSettingsGUI.fxml")); + rootStage.initStyle(StageStyle.UNDECORATED); + rootStage.initModality(Modality.WINDOW_MODAL); + rootStage.setResizable(false); + rootStage.setTitle("cryptoGX"); + Scene scene = new Scene(addSettingsRoot, 320, 605); + + rootStage.setScene(scene); + + scene.setOnMouseDragged(event -> { + rootStage.setX(event.getScreenX() + addConfigGUIX); + rootStage.setY(event.getScreenY() + addConfigGUIY); + }); + scene.setOnMousePressed(event -> { + addConfigGUIX = scene.getX() - event.getSceneX(); + addConfigGUIY = scene.getY() - event.getSceneY(); + }); + + Thread thread = new Thread(() -> { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + Platform.runLater(() -> { + MenuBar menuBar = (MenuBar) addSettingsRoot.lookup("#menuBar"); + menuBar.setOnMouseDragged(event -> { + rootStage.setX(event.getScreenX() + addConfigGUIX); + rootStage.setY(event.getScreenY() + addConfigGUIY); + }); + menuBar.setOnMousePressed(event -> { + addConfigGUIX = menuBar.getLayoutX() - event.getSceneX(); + addConfigGUIY = menuBar.getLayoutY() - event.getSceneY(); + }); + + ImageView closeButton = (ImageView) addSettingsRoot.lookup("#closeButton"); + closeButton.setOnMouseClicked(event -> rootStage.close()); + + TextField settingsNameEntry = (TextField) addSettingsRoot.lookup("#settingsNameEntry"); + + TextField textKeyEntry = (TextField) addSettingsRoot.lookup("#textKeyEntry"); + textKeyEntry.setText(userSetting.get("textKey")); + TextField textSaltEntry = (TextField) addSettingsRoot.lookup("#textSaltEntry"); + textSaltEntry.setText(userSetting.get("textSalt")); + ComboBox textAlgorithmBox = (ComboBox) addSettingsRoot.lookup("#textAlgorithmComboBox"); + textAlgorithmBox.setItems(FXCollections.observableArrayList(textAlgorithms)); + textAlgorithmBox.setValue(userSetting.get("textAlgorithm")); + + TextField fileEnDecryptKeyEntry = (TextField) addSettingsRoot.lookup("#fileEnDecryptKeyEntry"); + fileEnDecryptKeyEntry.setText(userSetting.get("fileEnDecryptKey")); + TextField fileEnDecryptSaltEntry = (TextField) addSettingsRoot.lookup("#fileEnDecryptSaltEntry"); + fileEnDecryptSaltEntry.setText(userSetting.get("fileEnDecryptSalt")); + ComboBox fileEnDecryptAlgorithmBox = (ComboBox) addSettingsRoot.lookup("#fileEnDecryptAlgorithmComboBox"); + fileEnDecryptAlgorithmBox.setItems(FXCollections.observableArrayList(fileEnDecryptAlgorithms)); + fileEnDecryptAlgorithmBox.setValue(userSetting.get("fileEnDecryptAlgorithm")); + + TextField fileDeleteIterationEntry = (TextField) addSettingsRoot.lookup("#fileDeleteIterationsEntry"); + fileDeleteIterationEntry.setText(userSetting.get("fileDeleteIterations")); + fileDeleteIterationEntry.textProperty().addListener((observable, oldValue, newValue) -> { + if (!newValue.matches("[0-9]*")) { + fileDeleteIterationEntry.setText(oldValue); + } + }); + + TextField fileOutputPathEntry = (TextField) addSettingsRoot.lookup("#fileOutputPathEntry"); + fileOutputPathEntry.setText(userSetting.get("fileOutputPath")); + CheckBox removeFromFileBoxCheckBox = (CheckBox) addSettingsRoot.lookup("#removeFromFileBoxCheckBox"); + removeFromFileBoxCheckBox.setSelected(Boolean.parseBoolean(userSetting.get("removeFromFileBox"))); + CheckBox limitNumberOfThreadsCheckBox = (CheckBox) addSettingsRoot.lookup("#limitNumberOfThreadsCheckBox"); + limitNumberOfThreadsCheckBox.setSelected(Boolean.parseBoolean(userSetting.get("limitNumberOfThreads"))); + + PasswordField hiddenPasswordEntry = (PasswordField) addSettingsRoot.lookup("#hiddenPasswordEntry"); + TextField showedPasswordEntry = (TextField) addSettingsRoot.lookup("#showedPasswordEntry"); + CheckBox showPassword = (CheckBox) addSettingsRoot.lookup("#showPassword"); + + showPassword.setOnAction(event -> { + if (showPassword.isSelected()) { + showedPasswordEntry.setText(hiddenPasswordEntry.getText()); + showedPasswordEntry.setVisible(true); + hiddenPasswordEntry.setVisible(false); + } else { + hiddenPasswordEntry.setText(showedPasswordEntry.getText()); + hiddenPasswordEntry.setVisible(true); + showedPasswordEntry.setVisible(false); + } + }); + CheckBox encryptSettings = (CheckBox) addSettingsRoot.lookup("#encryptSettings"); + encryptSettings.setOnAction(event -> { + if (encryptSettings.isSelected()) { + hiddenPasswordEntry.setDisable(false); + showedPasswordEntry.setDisable(false); + showPassword.setDisable(false); + } else { + hiddenPasswordEntry.setDisable(true); + showedPasswordEntry.setDisable(true); + showPassword.setDisable(true); + } + }); + Button saveButton = (Button) addSettingsRoot.lookup("#saveButton"); + saveButton.setOnAction(event -> { + if (settingsNameEntry.getText().trim().isEmpty()) { + warningAlert("Add a name for the setting"); + } else if (protectedConfigNames.contains(settingsNameEntry.getText())) { + warningAlert("Please choose another name for this setting"); + } else if (encryptSettings.isSelected()){ + try { + EnDecrypt.AES encrypt; + if (!hiddenPasswordEntry.isDisabled()) { + encrypt = new EnDecrypt.AES(hiddenPasswordEntry.getText(), new byte[16]); + newSettingItems.put("encryptHash", encrypt.encrypt(hiddenPasswordEntry.getText())); + } else { + encrypt = new EnDecrypt.AES(showedPasswordEntry.getText(), new byte[16]); + newSettingItems.put("encryptHash", encrypt.encrypt(showedPasswordEntry.getText())); + } + + newSettingItems.put("textKey", encrypt.encrypt(textKeyEntry.getText())); + newSettingItems.put("textSalt", encrypt.encrypt(textSaltEntry.getText())); + newSettingItems.put("textAlgorithm", encrypt.encrypt(textAlgorithmBox.getSelectionModel().getSelectedItem().toString())); + + newSettingItems.put("fileEnDecryptKey", encrypt.encrypt(fileEnDecryptKeyEntry.getText())); + newSettingItems.put("fileEnDecryptSalt", encrypt.encrypt(fileEnDecryptSaltEntry.getText())); + newSettingItems.put("fileEnDecryptAlgorithm", encrypt.encrypt(fileEnDecryptAlgorithmBox.getSelectionModel().getSelectedItem().toString())); + + newSettingItems.put("fileDeleteIterations", encrypt.encrypt(fileDeleteIterationEntry.getText())); + + newSettingItems.put("fileOutputPath", encrypt.encrypt(fileOutputPathEntry.getText())); + newSettingItems.put("removeFromFileBox", encrypt.encrypt(String.valueOf(removeFromFileBoxCheckBox.isSelected()))); + newSettingItems.put("limitNumberOfThreads", encrypt.encrypt(String.valueOf(limitNumberOfThreadsCheckBox.isSelected()))); + + addSetting(settingsNameEntry.getText(), newSettingItems); + + rootStage.close(); + } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidKeySpecException e) { + e.printStackTrace(); + } + } else { + newSettingItems.put("encryptHash", ""); + + newSettingItems.put("textKey", textKeyEntry.getText()); + newSettingItems.put("textSalt", textSaltEntry.getText()); + newSettingItems.put("textAlgorithm", textAlgorithmBox.getSelectionModel().getSelectedItem().toString()); + + newSettingItems.put("fileEnDecryptKey", fileEnDecryptKeyEntry.getText()); + newSettingItems.put("fileEnDecryptSalt", fileEnDecryptSaltEntry.getText()); + newSettingItems.put("fileEnDecryptAlgorithm", fileEnDecryptAlgorithmBox.getSelectionModel().getSelectedItem().toString()); + + newSettingItems.put("fileDeleteIterations", fileDeleteIterationEntry.getText()); + + newSettingItems.put("fileOutputPath", fileOutputPathEntry.getText()); + newSettingItems.put("removeFromFileBox", String.valueOf(removeFromFileBoxCheckBox.isSelected())); + newSettingItems.put("limitNumberOfThreads", String.valueOf(limitNumberOfThreadsCheckBox.isSelected())); + + addSetting(settingsNameEntry.getText(), newSettingItems); + + rootStage.close(); + } + }); + }); + }); + + thread.start(); + + rootStage.showAndWait(); + } + + /** + * <p>Shows a GUI where the user can export settings to a extra file</p> + * + * @param rootWindow from which this GUI will get called + * @throws IOException + */ + public static void exportSettingsGUI(Window rootWindow) throws IOException { + Stage rootStage = new Stage(); + rootStage.initOwner(rootWindow); + Parent exportSettingsRoot = FXMLLoader.load(Config.class.getResource("resources/exportSettingsGUI.fxml")); + rootStage.initStyle(StageStyle.UNDECORATED); + rootStage.initModality(Modality.WINDOW_MODAL); + rootStage.setResizable(false); + rootStage.setTitle("cryptoGX"); + Scene scene = new Scene(exportSettingsRoot, 254, 253); + + rootStage.setScene(scene); + + scene.setOnMouseDragged(event -> { + rootStage.setX(event.getScreenX() + addConfigGUIX); + rootStage.setY(event.getScreenY() + addConfigGUIY); + }); + scene.setOnMousePressed(event -> { + addConfigGUIX = scene.getX() - event.getSceneX(); + addConfigGUIY = scene.getY() - event.getSceneY(); + }); + + Thread thread = new Thread(() -> { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + MenuBar menuBar = (MenuBar) exportSettingsRoot.lookup("#menuBar"); + menuBar.setOnMouseDragged(event -> { + rootStage.setX(event.getScreenX() + addConfigGUIX); + rootStage.setY(event.getScreenY() + addConfigGUIY); + }); + menuBar.setOnMousePressed(event -> { + addConfigGUIX = menuBar.getLayoutX() - event.getSceneX(); + addConfigGUIY = menuBar.getLayoutY() - event.getSceneY(); + }); + ImageView closeButton = (ImageView) exportSettingsRoot.lookup("#closeButton"); + closeButton.setOnMouseClicked(event -> rootStage.close()); + + VBox settingsBox = (VBox) exportSettingsRoot.lookup("#settingsBox"); + Platform.runLater(() -> readUserSettings().keySet().forEach(s -> { + CheckBox newCheckBox = new CheckBox(); + newCheckBox.setText(s); + settingsBox.getChildren().add(newCheckBox); + })); + + Button exportButton = (Button) exportSettingsRoot.lookup("#exportButton"); + exportButton.setOnAction(event -> { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Export settings"); + fileChooser.setInitialFileName("settings.config"); + fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("Config files", "*.config", "*.xml"), + new FileChooser.ExtensionFilter("All files", "*.*")); + File file = fileChooser.showSaveDialog(exportSettingsRoot.getScene().getWindow()); + if (file != null) { + TreeMap<String, Map<String, String>> writeInfos = new TreeMap<>(); + TreeMap<String, Map<String, String>> settings = readUserSettings(); + for (int i=0; i<settingsBox.getChildren().size(); i++) { + CheckBox checkBox = (CheckBox) settingsBox.getChildren().get(i); + if (checkBox.isSelected()) { + String checkBoxText = checkBox.getText(); + writeInfos.put(checkBoxText, settings.get(checkBoxText)); + } + } + if (!file.getAbsolutePath().contains(".")) { + file = new File(file.getAbsolutePath() + ".config"); + } + writeConfig(file, writeInfos); + } + }); + }); + + thread.start(); + + rootStage.showAndWait(); + } + + /** + * <p>Shows a GUI where the user can load saved settings</p> + * + * @param rootWindow from which this GUI will get called + * @return the settings that the user has chosen + * @throws IOException + */ + public static TreeMap<String, Map<String, String>> loadSettingsGUI(Window rootWindow) throws IOException { + Button[] outerLoadButton = new Button[1]; + HashMap<String, String> setting = new HashMap<>(); + TreeMap<String, Map<String, String>> settingItems = readUserSettings(); + TreeMap<String, Map<String, String>> returnItems = new TreeMap<>(); + + Stage rootStage = new Stage(); + rootStage.initOwner(rootWindow); + AnchorPane loadSettingsRoot = FXMLLoader.load(Config.class.getResource("resources/loadSettingsGUI.fxml")); + rootStage.initStyle(StageStyle.UNDECORATED); + rootStage.initModality(Modality.WINDOW_MODAL); + rootStage.setResizable(false); + rootStage.setTitle("cryptoGX"); + rootStage.getIcons().add(new Image(Config.class.getResource("resources/cryptoGX.png").toExternalForm())); + Scene scene = new Scene(loadSettingsRoot, 242, 235); + + scene.setOnMouseDragged(event -> { + rootStage.setX(event.getScreenX() + addConfigGUIX); + rootStage.setY(event.getScreenY() + addConfigGUIY); + }); + scene.setOnMousePressed(event -> { + addConfigGUIX = scene.getX() - event.getSceneX(); + addConfigGUIY = scene.getY() - event.getSceneY(); + }); + + scene.setOnKeyReleased(event -> { + if (event.getCode() == KeyCode.ENTER) { + outerLoadButton[0].fire(); + } + }); + + Thread thread = new Thread(() -> { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + Platform.runLater(() -> { + MenuBar menuBar = (MenuBar) loadSettingsRoot.lookup("#menuBar"); + menuBar.setOnMouseDragged(event -> { + rootStage.setX(event.getScreenX() + addConfigGUIX); + rootStage.setY(event.getScreenY() + addConfigGUIY); + }); + menuBar.setOnMousePressed(event -> { + addConfigGUIX = menuBar.getLayoutX() - event.getSceneX(); + addConfigGUIY = menuBar.getLayoutY() - event.getSceneY(); + }); + + ImageView closeButton = (ImageView) loadSettingsRoot.lookup("#closeButton"); + if (settingItems.isEmpty()) { + rootStage.close(); + } + + closeButton.setOnMouseClicked(event -> { + setting.put("encryptHash", configDefaultEncryptHash); + + setting.put("textKey", configDefaultTextKey); + setting.put("textSalt", configDefaultTextSalt); + setting.put("textAlgorithm", configDefaultTextAlgorithm); + + setting.put("fileEnDecryptKey", configDefaultFileEnDecryptKey); + setting.put("fileEnDecryptSalt", configDefaultFileEnDecryptSalt); + setting.put("fileEnDecryptAlgorithm", configDefaultFileEnDecryptAlgorithm); + + setting.put("fileDeleteIterations", String.valueOf(configDefaultFileDeleteIterations)); + + setting.put("fileOutputPath", configDefaultFileOutputPath); + setting.put("removeFromFileBox", String.valueOf(configDefaultRemoveFileFromFileBox)); + setting.put("limitNumberOfThreads", String.valueOf(configDefaultLimitNumberOfThreads)); + + returnItems.put("default", setting); + + rootStage.close(); + }); + + PasswordField keyHideEntry = (PasswordField) loadSettingsRoot.lookup("#passwordEntryHide"); + TextField keyShowEntry = (TextField) loadSettingsRoot.lookup("#passwordEntryShow"); + + CheckBox showPassword = (CheckBox) loadSettingsRoot.lookup("#showPassword"); + showPassword.setOnAction(event -> { + if (showPassword.isSelected()) { + keyShowEntry.setText(keyHideEntry.getText()); + keyShowEntry.setVisible(true); + keyHideEntry.setVisible(false); + } else { + keyHideEntry.setText(keyShowEntry.getText()); + keyHideEntry.setVisible(true); + keyShowEntry.setVisible(false); + } + }); + + ComboBox settingsBox = (ComboBox) loadSettingsRoot.lookup("#settingsBox"); + settingsBox.setItems(FXCollections.observableArrayList(settingItems.keySet())); + settingsBox.setValue(settingItems.firstKey()); + if (settingItems.firstEntry().getValue().get("encryptHash").trim().isEmpty()) { + keyHideEntry.clear(); + keyHideEntry.setDisable(true); + keyShowEntry.setDisable(true); + showPassword.setDisable(true); + } + settingsBox.setOnAction(event -> { + try { + if (settingItems.get(settingsBox.getSelectionModel().getSelectedItem().toString()).get("encryptHash").trim().isEmpty()) { + keyHideEntry.clear(); + keyHideEntry.setDisable(true); + keyShowEntry.clear(); + keyShowEntry.setDisable(true); + showPassword.setDisable(true); + } else { + keyHideEntry.clear(); + keyHideEntry.setDisable(false); + keyShowEntry.clear(); + keyShowEntry.setDisable(false); + showPassword.setDisable(false); + } + } catch (NullPointerException e) { + //get called when delete button is pressed + } + }); + + Button loadButton = (Button) loadSettingsRoot.lookup("#loadButton"); + loadButton.setOnAction(event -> { + String settingName = settingsBox.getSelectionModel().getSelectedItem().toString(); + Map<String, String> selectedSetting = settingItems.get(settingName); + if (keyHideEntry.isDisabled() && showPassword.isDisabled() && showPassword.isDisabled()) { + setting.put("encryptHash", ""); + + setting.put("textKey", selectedSetting.get("textKey")); + setting.put("textSalt", selectedSetting.get("textSalt")); + setting.put("textAlgorithm", selectedSetting.get("textAlgorithm")); + + setting.put("fileEnDecryptKey", selectedSetting.get("fileEnDecryptKey")); + setting.put("fileEnDecryptSalt", selectedSetting.get("fileEnDecryptSalt")); + setting.put("fileEnDecryptAlgorithm", selectedSetting.get("fileEnDecryptAlgorithm")); + + setting.put("fileDeleteIterations", selectedSetting.get("fileDeleteIterations")); + + setting.put("fileOutputPath", selectedSetting.get("fileOutputPath")); + setting.put("removeFromFileBox", selectedSetting.get("removeFromFileBox")); + setting.put("limitNumberOfThreads", selectedSetting.get("limitNumberOfThreads")); + + returnItems.put(settingsBox.getSelectionModel().getSelectedItem().toString(), setting); + + rootStage.close(); + } else { + EnDecrypt.AES decryptSetting; + if (keyHideEntry.isVisible()) { + decryptSetting = new EnDecrypt.AES(keyHideEntry.getText(), new byte[16]); + } else { + decryptSetting = new EnDecrypt.AES(keyShowEntry.getText(), new byte[16]); + } + try { + if (keyHideEntry.isVisible() && !decryptSetting.encrypt(keyHideEntry.getText()).equals(settingItems.get(settingsBox.getSelectionModel().getSelectedItem().toString()).get("encryptHash").trim())) { + warningAlert("Wrong key is given"); + } else if (keyShowEntry.isVisible() && !decryptSetting.encrypt(keyShowEntry.getText()).equals(settingItems.get(settingsBox.getSelectionModel().getSelectedItem().toString()).get("encryptHash").trim())) { + warningAlert("Wrong key is given"); + } else { + Map<String, String> selectedEncryptedSetting = settingItems.get(settingName); + setting.put("textKey", decryptSetting.decrypt(selectedEncryptedSetting.get("textKey"))); + setting.put("textSalt", decryptSetting.decrypt(selectedEncryptedSetting.get("textSalt"))); + setting.put("textAlgorithm", decryptSetting.decrypt(selectedEncryptedSetting.get("textAlgorithm"))); + + setting.put("fileEnDecryptKey", decryptSetting.decrypt(selectedEncryptedSetting.get("fileEnDecryptKey"))); + setting.put("fileEnDecryptSalt", decryptSetting.decrypt(selectedEncryptedSetting.get("fileEnDecryptSalt"))); + setting.put("fileEnDecryptAlgorithm", decryptSetting.decrypt(selectedEncryptedSetting.get("fileEnDecryptAlgorithm"))); + + setting.put("fileDeleteIterations", String.valueOf(Integer.parseInt(decryptSetting.decrypt(selectedEncryptedSetting.get("fileDeleteIterations"))))); + + setting.put("fileOutputPath", decryptSetting.decrypt(selectedEncryptedSetting.get("fileOutputPath"))); + setting.put("removeFromFileBox", decryptSetting.decrypt(selectedEncryptedSetting.get("removeFromFileBox"))); + setting.put("limitNumberOfThreads", decryptSetting.decrypt(selectedEncryptedSetting.get("limitNumberOfThreads"))); + + returnItems.put(settingsBox.getSelectionModel().getSelectedItem().toString(), setting); + + rootStage.close(); + } + } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidKeySpecException e) { + e.printStackTrace(); + warningAlert("Wrong key is given"); + } + } + }); + outerLoadButton[0] = loadButton; + + Button deleteButton = (Button) loadSettingsRoot.lookup("#deleteButton"); + deleteButton.setOnAction(event -> { + AtomicReference<Double> deleteQuestionX = new AtomicReference<>((double) 0); + AtomicReference<Double> deleteQuestionY = new AtomicReference<>((double) 0); + Alert deleteQuestion = new Alert(Alert.AlertType.CONFIRMATION, "Delete " + settingsBox.getSelectionModel().getSelectedItem().toString() + "?", ButtonType.OK, ButtonType.CANCEL); + deleteQuestion.initStyle(StageStyle.UNDECORATED); + deleteQuestion.setTitle("Confirmation"); + ((Stage) deleteQuestion.getDialogPane().getScene().getWindow()).getIcons().add(new Image(Config.class.getResource("resources/cryptoGX.png").toExternalForm())); + + Scene window = deleteQuestion.getDialogPane().getScene(); + + window.setOnMouseDragged(dragEvent -> { + deleteQuestion.setX(dragEvent.getScreenX() + deleteQuestionX.get()); + deleteQuestion.setY(dragEvent.getScreenY() + deleteQuestionY.get()); + }); + window.setOnMousePressed(pressEvent -> { + deleteQuestionX.set(window.getX() - pressEvent.getSceneX()); + deleteQuestionY.set(window.getY() - pressEvent.getSceneY()); + }); + + Optional<ButtonType> result = deleteQuestion.showAndWait(); + if (result.get() == ButtonType.OK) { + deleteUserSetting(settingsBox.getSelectionModel().getSelectedItem().toString()); + settingItems.clear(); + settingItems.putAll(readUserSettings()); + if (settingItems.size() == 0) { + for (int i=0; i<100; i++) { + if (config.isFile()) { + if (config.delete()) { + isConfig = false; + rootStage.close(); + break; + } + } + } + rootStage.close(); + return; + } + settingsBox.setItems(FXCollections.observableArrayList(settingItems.keySet())); + settingsBox.setValue(settingItems.firstKey()); + } + }); + }); + }); + + thread.start(); + + rootStage.setScene(scene); + rootStage.showAndWait(); + + return returnItems; + } + + /** + * <p>Shows a GUI where the user can save the current settings</p> + * + * @param settingName name of the new setting + * @param userSetting the current settings + */ + public static void addSetting(String settingName, Map<String, String> userSetting) { + TreeMap<String, Map<String, String>> newConfig = new TreeMap<>(readUserSettings()); + newConfig.put(settingName, userSetting); + writeConfig(newConfig); + } + + /** + * <p>Shows a GUI where the user can save the current settings</p> + * + * @param settingName name of the new setting + * @param userSetting the current settings + * @param encryptPassword to encrypt the settings + */ + public static void addSetting(String settingName, Map<String, String> userSetting, String encryptPassword) { + TreeMap<String, Map<String, String>> newConfig = new TreeMap<>(readUserSettings()); + newConfig.put(settingName, userSetting); + writeConfig(newConfig, Collections.singletonMap(settingName, encryptPassword)); + } + + /** + * <p>Deletes a saved setting</p> + * + * @param name of the setting + * @return if the setting could be found + */ + public static boolean deleteUserSetting(String name) { + TreeMap<String, Map<String, String>> newSetting = new TreeMap<>(); + TreeMap<String, Map<String, String>> oldSetting = readUserSettings(); + boolean found = false; + + for (Map.Entry<String, Map<String, String>> entry: oldSetting.entrySet()) { + if (!entry.getKey().equals(name)) { + newSetting.put(entry.getKey(), entry.getValue()); + } else { + found = true; + } + } + writeConfig(newSetting); + return found; + } + + public static TreeMap<String, Map<String, String>> readUserSettings() { + return readUserSettings(config); + } + + /** + * @see Config#readUserSettings(String) + */ + public static TreeMap<String, Map<String, String>> readUserSettings(File file) { + TreeMap<String, Map<String, String>> rootInfos = new TreeMap<>(); + try { + XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory(); + XMLStreamReader xmlStreamReader; + try { + xmlStreamReader = xmlInputFactory.createXMLStreamReader(new FileInputStream(file)); + } catch (FileNotFoundException e) { + return rootInfos; + } + + HashMap<String, String> infos = new HashMap<>(); + + String infoName = null; + StringBuilder infoCharacters = new StringBuilder(); + String rootName = null; + + while (xmlStreamReader.hasNext()) { + + int eventType = xmlStreamReader.next(); + + switch (eventType) { + case XMLStreamReader.START_ELEMENT: + String startTag = xmlStreamReader.getLocalName().trim(); + if (startTag != null) { + if (protectedConfigNames.contains(startTag)) { + continue; + } else if (rootName == null) { + rootName = startTag; + } else { + infoName = startTag; + } + } + break; + case XMLStreamReader.CHARACTERS: + if (infoName != null) { + if (!xmlStreamReader.getText().trim().equals("")) { + infoCharacters.append(xmlStreamReader.getText()); + } + } + break; + case XMLStreamReader.END_ELEMENT: + String endTag = xmlStreamReader.getLocalName().trim(); + if (endTag != null) { + if (protectedConfigNames.contains(endTag)) { + continue; + } else if (endTag.equals(rootName)) { + rootInfos.put(rootName, infos); + rootName = null; + infos = new HashMap<>(); + infoCharacters = new StringBuilder(); + } else { + infos.put(infoName, infoCharacters.toString()); + infoName = null; + infoCharacters = new StringBuilder(); + } + } + break; + } + } + xmlStreamReader.close(); + } catch (XMLStreamException e) { + e.printStackTrace(); + } + System.out.println(rootInfos); + + return rootInfos; + } + + /** + * <p>Shows a GUI where the user can choose and load saved settings </p> + * + * @param filename of the file with the settings + * @return the setting that the user has chosen + */ + public static TreeMap<String, Map<String, String>> readUserSettings(String filename) { + return readUserSettings(new File(filename)); + } + + /** + * <p>Writes settings (could be more than one) to the pre-defined config file</p> + * + * @see Config#writeConfig(File, TreeMap, Map) + */ + public static void writeConfig(TreeMap<String, Map<String, String>> userSettings) { + writeConfig(config, userSettings, null); + } + + /** + * <p>Writes settings (could be more than one) to the pre-defined config file</p> + * + * @see Config#writeConfig(File, TreeMap, Map) + */ + public static void writeConfig(TreeMap<String, Map<String, String>> userSettings, Map<String, String> encryptedSettings) { + writeConfig(config, userSettings, encryptedSettings); + } + + /** + * <p>Writes settings (could be more than one) to the pre-defined config file</p> + * + * @see Config#writeConfig(String, TreeMap, Map) + */ + public static void writeConfig(String filename, TreeMap<String, Map<String, String>> userSettings) { + writeConfig(filename, userSettings, null); + } + + /** + * <p>Writes settings (could be more than one) to the pre-defined config file</p> + * + * @see Config#writeConfig(File, TreeMap, Map) + */ + public static void writeConfig(File file, TreeMap<String, Map<String, String>> userSettings) { + writeConfig(file, userSettings, null); + } + + /** + * <p>Writes settings (could be more than one) to a file</p> + * + * @see Config#writeConfig(String, TreeMap, Map) + */ + public static void writeConfig(String filename, TreeMap<String, Map<String, String>> userSettings, Map<String, String> encryptedSettings) { + writeConfig(new File(filename), userSettings, encryptedSettings); + } + + /** + * <p>Writes settings (could be more than one) to a file</p> + * + * @param file where the settings should be written in + * @param userSettings of the user + * @param encryptedSettings says which settings from {@param userSettings} should be encrypted with a password + */ + public static void writeConfig(File file, TreeMap<String, Map<String, String>> userSettings, Map<String, String> encryptedSettings) { + EnDecrypt.AES encryptSetting; + StringWriter stringWriter = new StringWriter(); + + if (encryptedSettings == null) { + encryptedSettings = new HashMap<>(); + } + + try { + XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance(); + XMLStreamWriter xmlStreamWriter = xmlOutputFactory.createXMLStreamWriter(stringWriter); + + xmlStreamWriter.writeStartDocument(); + xmlStreamWriter.writeStartElement("cryptoGX"); + for (Map.Entry<String, Map<String, String>> settingElement: userSettings.entrySet()) { + xmlStreamWriter.writeStartElement(settingElement.getKey()); + if (encryptedSettings.containsKey(settingElement.getKey())) { + encryptSetting = new EnDecrypt.AES(settingElement.getKey(), new byte[16]); + for (Map.Entry<String, String> entry: settingElement.getValue().entrySet()) { + xmlStreamWriter.writeStartElement(entry.getKey()); + xmlStreamWriter.writeCharacters(encryptSetting.encrypt(entry.getValue())); + xmlStreamWriter.writeEndElement(); + } + } else { + for (Map.Entry<String, String> entry: settingElement.getValue().entrySet()) { + xmlStreamWriter.writeStartElement(entry.getKey()); + xmlStreamWriter.writeCharacters(entry.getValue()); + xmlStreamWriter.writeEndElement(); + } + } + xmlStreamWriter.writeEndElement(); + } + xmlStreamWriter.writeEndElement(); + xmlStreamWriter.writeEndDocument(); + + //prettify + + Source xmlInput = new StreamSource(new StringReader(stringWriter.toString())); + StringWriter prettifyStringWriter = new StringWriter(); + StreamResult xmlOutput = new StreamResult(prettifyStringWriter); + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformerFactory.setAttribute("indent-number", 2); + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.transform(xmlInput, xmlOutput); + + BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file)); + for (String s: prettifyStringWriter.getBuffer().toString().split(System.lineSeparator())) { + bufferedWriter.write(s); + bufferedWriter.newLine(); + } + bufferedWriter.close(); + } catch (XMLStreamException | IOException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidKeySpecException | TransformerException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/org/blueshard/cryptogx/Controller.java b/src/org/blueshard/cryptogx/Controller.java new file mode 100644 index 0000000..2b6c493 --- /dev/null +++ b/src/org/blueshard/cryptogx/Controller.java @@ -0,0 +1,1581 @@ +package org.blueshard.cryptogx; + +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.event.Event; +import javafx.fxml.Initializable; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuBar; +import javafx.scene.control.MenuItem; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.*; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.stage.*; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.image.BufferedImage; +import java.io.*; +import java.net.*; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.*; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.blueshard.cryptogx.Config.*; +import static org.blueshard.cryptogx.Main.*; + +public class Controller implements Initializable { + + private Event fileEnDecryptLabelEvent; + + private double menubarX, menubarY; + private boolean textLoading = false; + private boolean fileEnDecryptLoading = false; + private boolean fileDeleteLoading = false; + private AtomicInteger textThreads = new AtomicInteger(0); + private AtomicInteger totalThreads = new AtomicInteger(0); + private int tooltipShow = 15; + private final int DATAFILEURL = 2; + private final int FILEFILEURL = 1; + private final int NONSPECIFICFILEURL = 0; + private final int IMAGE = 78345; + private final int FILE = 23902; + private final int UNKNOWN = 12345; + private final KeyCombination paste = new KeyCodeCombination(KeyCode.V, KeyCombination.CONTROL_DOWN); + private final Image loadingImage = new Image(getClass().getResource("resources/loading.gif").toExternalForm()); + + private HashMap<String, String> currentConfigSettings = new HashMap<>(); + + private HashMap<Label, ArrayList<File>> enDecryptInputOutputFiles = new HashMap<>(); + private HashMap<Label, ArrayList<Object>> enDecryptInputOutputInternetFiles = new HashMap<>(); + private HashMap<Label, BufferedImage> enDecryptInputOutputClipboardImages = new HashMap<>(); + private HashMap<Label, File> deleteInputFiles = new HashMap<>(); + private List<Thread> fileEnDecryptThreads = Collections.synchronizedList(new ArrayList<>()); + private List<Thread> fileDeleteThreads = Collections.synchronizedList(new ArrayList<>()); + + private ContextMenu fileEnDecryptInputContextMenu = new ContextMenu(); + private ContextMenu fileDeleteInputContextMenu = new ContextMenu(); + private Label choosedLabel = null; + private String choosedLabelType = null; + private MenuItem fileOutputFileChangeDest = new MenuItem("Change output file"); + private MenuItem getChoosedLabelInputFileFolder = new MenuItem("Open source directory"); + private MenuItem getChoosedLabelOutputFileFolder = new MenuItem("Open source directory"); + private Tooltip tooltip = new Tooltip(); + + public AnchorPane rootWindow; + + public Button fileEnDecryptFilesButton; + public Button fileDecrypt; + public Button fileEncrypt; + public Button fileEnDecryptStop; + + public ComboBox<String> textAlgorithmBox; + public ComboBox<String> fileEnDecryptAlgorithmBox; + + public ImageView minimizeWindow; + public ImageView closeWindow; + public ImageView textLoadingImage; + public ImageView fileEnDecryptLoadingImage; + public ImageView fileDeleteLoadingImage; + + public Menu settingsMenu; + + public MenuBar menubar; + + public MenuItem setDefaultOutputPath; + public MenuItem saveSettings; + public MenuItem loadSettings; + public MenuItem exportSettings; + public MenuItem importSettings; + + public RadioMenuItem removeFileFromFileBox; + public RadioMenuItem limitNumberOfThreads; + + public ScrollPane fileEnDecryptInputScroll; + + public TextArea textDecryptedEntry; + public TextArea textEncryptedEntry; + + public TextField textKeyEntry; + public TextField textSaltEntry; + public TextField fileEnDecryptKeyEntry; + public TextField fileDecryptOutputFile; + public TextField fileEncryptOutputFile; + public TextField fileEnDecryptSaltEntry; + public TextField fileDeleteIterationsEntry; + + public VBox fileEnDecryptInputFiles; + public VBox fileDeleteInputFiles; + + //-----general-----// + + /** + * <p>Shows a tooltip when the user type in some text in a text field, text area, etc. and the mouse is over this entry</p> + * + * @param event from which this method is called + */ + public void keyTypedTooltip(KeyEvent event) { + String id = null; + String text = ""; + try { + id = ((TextField) event.getSource()).getId(); + text = ((TextField) event.getSource()).getText() + event.getCharacter(); + tooltip.setText(text); + } catch (ClassCastException e) { + tooltip.setText(((TextArea) event.getSource()).getText() + event.getCharacter()); + } + if (id != null) { + switch (id) { + case ("textKeyEntry"): + currentConfigSettings.replace("textKey", text); + break; + case ("textSaltEntry"): + currentConfigSettings.replace("textSalt", text); + break; + case ("fileEnDecryptKeyEntry"): + currentConfigSettings.replace("fileEnDecryptKey", text); + break; + case ("fileEnDecryptSaltEntry"): + currentConfigSettings.replace("fileEnDecryptSalt", text); + break; + case ("fileDeleteIterationsEntry"): + currentConfigSettings.replace("fileDeleteIterations", String.valueOf(Integer.parseInt(text))); + break; + } + } + } + + /** + * <p>Shows a tooltip when to mouse is over a text field, text area, etc.</p> + * + * @param event from which this method is called + */ + public void mouseOverEntryTooltip(MouseEvent event) { + try { + tooltip.setText(((TextField) event.getSource()).getText()); + } catch (ClassCastException e) { + try { + tooltip.setText(((TextArea) event.getSource()).getText()); + } catch (ClassCastException ex) { + tooltip.setText(((Label) event.getSource()).getText()); + } + } + if (!tooltip.getText().trim().isEmpty()) { + tooltip.show(rootWindow.getScene().getWindow(), event.getScreenX(), event.getScreenY() + tooltipShow); + } + } + + /** + * <p>Hides the tooltip if the mouse exit a text field, text area, etc.</p> + */ + public void mouseExitEntryTooltip() { + tooltip.hide(); + } + + //-----root-----// + + /** + * <p>Closed the application. + * Get called if red close button is pressed</p> + */ + public void closeApplication() { + Stage rootStage = (Stage) rootWindow.getScene().getWindow(); + rootStage.close(); + System.exit(0); + } + + /** + * <p>Hides the application. + * Get called if the green minimize button is pressed</p> + */ + public void minimizeApplication() { + Stage rootStage = (Stage) rootWindow.getScene().getWindow(); + rootStage.setIconified(true); + } + + //-----text-----// + + /** + * <p>Encrypt text in {@link Controller#textDecryptedEntry}. + * Get called if the text 'Encrypt' button is pressed</p> + */ + public void textEncryptButton() { + final byte[] salt; + if (!textSaltEntry.getText().isEmpty()) { + salt = textSaltEntry.getText().getBytes(StandardCharsets.UTF_8); + } else { + salt = new byte[16]; + } + if (!textLoading) { + textLoadingImage.setImage(loadingImage); + } + Thread textEncrypt = new Thread(() -> { + textThreads.getAndIncrement(); + if (limitNumberOfThreads.isSelected()) { + while (totalThreads.get() >= Runtime.getRuntime().availableProcessors()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().stop(); + } + } + } + totalThreads.getAndIncrement(); + EnDecrypt.AES encrypt = new EnDecrypt.AES(textKeyEntry.getText(), salt); + try { + String encryptedText = encrypt.encrypt(textDecryptedEntry.getText()); + Platform.runLater(() -> { + textEncryptedEntry.setText(encryptedText); + }); + } catch (NoSuchPaddingException | InvalidKeySpecException | InvalidKeyException | NoSuchAlgorithmException | BadPaddingException e) { + e.printStackTrace(); + } catch (IllegalArgumentException | IllegalBlockSizeException e) { + e.printStackTrace(); + Platform.runLater(() -> errorAlert("Wrong text for encryption is given", e.getMessage())); + } + if ((textThreads.get() - 1) <= 0) { + textLoadingImage.setImage(null); + textLoading = false; + } + textThreads.getAndDecrement(); + totalThreads.getAndDecrement(); + }); + textEncrypt.setDaemon(false); + textEncrypt.start(); + textLoading = true; + } + + /** + * <p>Decrypt text in {@link Controller#textEncryptedEntry}. + * Get called if the text 'Decrypt' button is pressed</p> + */ + public void textDecryptButton() { + final byte[] salt; + if (!textSaltEntry.getText().isEmpty()) { + salt = textSaltEntry.getText().getBytes(StandardCharsets.UTF_8); + } else { + salt = new byte[16]; + } + if (!textLoading) { + textLoadingImage.setImage(loadingImage); + } + Thread textDecrypt = new Thread(() -> { + textThreads.getAndIncrement(); + if (limitNumberOfThreads.isSelected()) { + while (totalThreads.get() >= Runtime.getRuntime().availableProcessors()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().stop(); + } + } + } + totalThreads.getAndIncrement(); + EnDecrypt.AES decrypt = new EnDecrypt.AES(textKeyEntry.getText(), salt); + try { + String DecryptedText = decrypt.decrypt(textEncryptedEntry.getText()); + Platform.runLater(() -> { + textDecryptedEntry.setText(DecryptedText); + }); + } catch (NoSuchPaddingException | InvalidKeySpecException | InvalidKeyException | NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (BadPaddingException e) { + e.printStackTrace(); + Platform.runLater(() -> errorAlert("Wrong key and / or salt is given", e.getMessage())); + } catch (IllegalArgumentException | IllegalBlockSizeException e) { + e.printStackTrace(); + Platform.runLater(() -> errorAlert("Wrong text for decryption is given", e.getMessage())); + } + if ((textThreads.get() - 1) <= 0) { + textLoading = false; + Platform.runLater(() -> textLoadingImage.setImage(null)); + } + textThreads.getAndDecrement(); + totalThreads.getAndDecrement(); + }); + textDecrypt.setDaemon(false); + textDecrypt.start(); + textLoading = true; + } + + //-----fileEnDecrypt-----// + + /** + * <p>Synchronized method to get the list of threads which en- / decrypt files</p> + * + * @return list of en- / decryption threads + */ + private synchronized List<Thread> getFileEnDecryptThreads() { + return fileEnDecryptThreads; + } + + /** + * <p>Synchronized method to get the number of threads which en- / decrypt files</p> + * + * @return number of en- / decryption threads + */ + private synchronized int getFileEnDecryptThreadsSize() { + return fileEnDecryptThreads.size(); + } + + /** + * <p>Synchronized method to add a thread to the file en- / decryption list of current running file en- / decryption threads</p> + * + * @param thread that should be added + */ + private synchronized void addFileEnDecryptThread(Thread thread) { + fileEnDecryptThreads.add(thread); + } + + /** + * <p>Synchronized method to remove a thread from the file en- / decryption list of current running file en- / decryption threads</p> + * + * @param thread that should be removed + */ + private synchronized void removeFileEnDecryptThread(Thread thread) { + fileEnDecryptThreads.remove(thread); + } + + /** + * <p>Adds a file for en- / decryption</p> + * + * @param file that should be added + */ + private void fileEnDecryptAddFile(File file) { + for (Label l: enDecryptInputOutputFiles.keySet()) { + if (l.getText().equals(file.getAbsolutePath())) { + return; + } + } + Label newLabel = new Label(file.getAbsolutePath()); + newLabel.setOnKeyTyped(this::keyTypedTooltip); + newLabel.setOnMouseMoved(this::mouseOverEntryTooltip); + newLabel.setOnMouseExited(event -> mouseExitEntryTooltip()); + newLabel.setOnMouseClicked(event -> { + fileEnDecryptSelected(newLabel); + fileOutputFilesChangeText(newLabel, null, null); + fileEnDecryptLabelEvent = event; + }); + newLabel.setContextMenu(fileEnDecryptInputContextMenu); + String fileAbsolutePath = file.getAbsolutePath(); + String fileName = file.getName(); + + File encryptFile; + File decryptFile; + ArrayList<File> inputOutputList = new ArrayList<>(); + if (currentConfigSettings.get("fileOutputPath").trim().isEmpty()) { + encryptFile = new File(fileAbsolutePath + ".cryptoGX"); + while (encryptFile.isFile()) { + encryptFile = new File(encryptFile.getAbsolutePath() + ".cryptoGX"); + } + if (fileAbsolutePath.endsWith(".cryptoGX")) { + decryptFile = new File(fileAbsolutePath.substring(0, fileAbsolutePath.length() - 9)); + if (decryptFile.isFile()) { + while (decryptFile.isFile()) { + decryptFile = new File(decryptFile.getAbsolutePath() + ".cryptoGX"); + } + } + } else { + decryptFile = new File(fileAbsolutePath + ".cryptoGX"); + while (decryptFile.isFile()) { + decryptFile = new File(decryptFile.getAbsolutePath() + ".cryptoGX"); + } + } + } else { + encryptFile = new File(currentConfigSettings.get("fileOutputPath").trim() + "/" + fileName + ".cryptoGX"); + while (encryptFile.isFile()) { + encryptFile = new File(encryptFile.getAbsolutePath() + ".cryptoGX"); + } + if (fileAbsolutePath.endsWith(".cryptoGX")) { + decryptFile = new File(currentConfigSettings.get("fileOutputPath").trim() + "/" + fileName.substring(0, fileAbsolutePath.length() - 9)); + if (decryptFile.isFile()) { + while (decryptFile.isFile()) { + decryptFile = new File(decryptFile.getAbsolutePath() + ".cryptoGX"); + } + } + } else { + decryptFile = new File(currentConfigSettings.get("fileOutputPath").trim() + "/" + fileName + ".cryptoGX"); + while (decryptFile.isFile()) { + decryptFile = new File(decryptFile.getAbsolutePath() + ".cryptoGX"); + } + } + } + System.out.println(encryptFile.getAbsolutePath() + ";" + decryptFile.getAbsolutePath()); + inputOutputList.add(0, encryptFile); + inputOutputList.add(1, decryptFile); + fileEnDecryptInputFiles.getChildren().add(newLabel); + enDecryptInputOutputFiles.put(newLabel, inputOutputList); + } + + /** + * <p>Adds an file from the internet for en- / decryption</p> + * + * @param url of the file + * @param fileType of the file + * @throws URISyntaxException + */ + private void fileEnDecryptAddInternetFile(String url, int fileType) throws URISyntaxException { + String filename; + if (fileType == FILEFILEURL) { + filename = url.substring(url.lastIndexOf("/") + 1); + } else if (fileType == DATAFILEURL) { + filename = url.substring(5, url.indexOf("/")) + "." + url.substring(url.indexOf("/") + 1, url.indexOf(";")); + } else if (fileType == NONSPECIFICFILEURL) { + filename = "unknown" + System.nanoTime(); + } else { + warningAlert("Cannot read given url '" + url + "'"); + return; + } + for (Label l: enDecryptInputOutputInternetFiles.keySet()) { + if (l.getText().equals(filename)) { + return; + } + } + Label newLabel = new Label(filename); + newLabel.setOnKeyTyped(this::keyTypedTooltip); + newLabel.setOnMouseMoved(this::mouseOverEntryTooltip); + newLabel.setOnMouseExited(event -> mouseExitEntryTooltip()); + newLabel.setOnMouseClicked(event -> { + fileEnDecryptSelected(newLabel); + fileOutputFilesChangeText(newLabel, null, null); + fileEnDecryptLabelEvent = event; + }); + newLabel.setContextMenu(fileEnDecryptInputContextMenu); + + File encryptFile; + File decryptFile; + ArrayList<Object> fileSpecs = new ArrayList<>(); + ArrayList<File> inputOutputFiles = new ArrayList<>(); + fileSpecs.add(0, fileType); + fileSpecs.add(1, url); + String currentDir = this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().getPath(); + + if (currentConfigSettings.get("fileOutputPath").trim().isEmpty()) { + encryptFile = new File(currentDir + "/" + filename + ".cryptoGX"); + while (encryptFile.isFile()) { + encryptFile = new File(encryptFile.getAbsolutePath() + ".cryptoGX"); + } + if (url.endsWith(".cryptoGX") && filename.endsWith(".cryptoGX")) { + decryptFile = new File(currentDir + "/" + filename.substring(0, filename.length() - 9)); + } else { + decryptFile = new File(currentDir + "/" + filename); + } + } else { + encryptFile = new File(currentConfigSettings.get("fileOutputPath") + "/" + filename + ".cryptoGX"); + while (encryptFile.isFile()) { + encryptFile = new File(encryptFile.getAbsolutePath() + ".cryptoGX"); + } + if (url.endsWith(".cryptoGX") && filename.endsWith(".cryptoGX")) { + decryptFile = new File(currentConfigSettings.get("fileOutputPath") + "/" + filename.substring(0, filename.length() - 9)); + } else { + decryptFile = new File(currentConfigSettings.get("fileOutputPath") + "/" + filename); + } + } + while (decryptFile.isFile()) { + decryptFile = new File(decryptFile.getAbsolutePath() + ".cryptoGX"); + } + inputOutputFiles.add(0, encryptFile); + inputOutputFiles.add(1, decryptFile); + + fileEnDecryptInputFiles.getChildren().add(newLabel); + enDecryptInputOutputInternetFiles.put(newLabel, fileSpecs); + enDecryptInputOutputFiles.put(newLabel, inputOutputFiles); + } + + /** + * <p>Adds an clipboard image for en- / decryption. + * This can be a normal image and an image stream</p> + * + * @param image that should be added + * @throws URISyntaxException + */ + private void fileEnDecryptAddClipboardImage(BufferedImage image) throws URISyntaxException { + String filename = "clipboardImage" + System.nanoTime() + ".png"; + for (Label l: enDecryptInputOutputClipboardImages.keySet()) { + if (l.getText().equals(filename)) { + return; + } + } + Label newLabel = new Label(filename); + newLabel.setOnKeyTyped(this::keyTypedTooltip); + newLabel.setOnMouseMoved(this::mouseOverEntryTooltip); + newLabel.setOnMouseExited(event -> mouseExitEntryTooltip()); + newLabel.setOnMouseClicked(event -> { + fileEnDecryptSelected(newLabel); + fileOutputFilesChangeText(newLabel, null, null); + fileEnDecryptLabelEvent = event; + }); + newLabel.setContextMenu(fileEnDecryptInputContextMenu); + + File encryptFile; + File decryptFile; + String currentDir = this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().getPath(); + ArrayList<File> inputOutputFiles = new ArrayList<>(); + + if (currentConfigSettings.get("fileOutputPath").trim().isEmpty()) { + encryptFile = new File(currentDir + "/" + filename + ".cryptoGX"); + decryptFile = new File(currentDir + "/" + filename); + } else { + encryptFile = new File(currentConfigSettings.get("fileOutputPath").trim() + "/" + filename + ".cryptoGX"); + decryptFile = new File(currentConfigSettings.get("fileOutputPath").trim() + "/" + filename); + } + while (encryptFile.isFile()) { + encryptFile = new File(encryptFile.getAbsolutePath() + ".cryptoGX"); + } + while (decryptFile.isFile()) { + decryptFile = new File(decryptFile.getAbsolutePath() + ".cryptoGX"); + } + inputOutputFiles.add(0, encryptFile); + inputOutputFiles.add(1, decryptFile); + + fileEnDecryptInputFiles.getChildren().add(newLabel); + enDecryptInputOutputClipboardImages.put(newLabel, image); + enDecryptInputOutputFiles.put(newLabel, inputOutputFiles); + } + + /** + * <p>Changes the text in the file en- / decryption output file text fields</p> + * + * @param label + * @param encryptOutputFile is the filename of the file it gets encrypted + * @param decryptOutputFile is the filename of the file it gets decrypted + */ + private void fileOutputFilesChangeText(Label label, String encryptOutputFile, String decryptOutputFile) { + File encryptFile; + File decryptFile; + ArrayList<File> change = new ArrayList<>(); + if (encryptOutputFile == null) { + encryptFile = enDecryptInputOutputFiles.get(label).get(0); + } else { + encryptFile = new File(encryptOutputFile); + } + if (decryptOutputFile == null) { + decryptFile = enDecryptInputOutputFiles.get(label).get(1); + } else { + decryptFile = new File(decryptOutputFile); + } + change.add(0, encryptFile); + change.add(1, decryptFile); + if (encryptFile.toString().trim().isEmpty()) { + fileEncryptOutputFile.setText(""); + } else { + fileEncryptOutputFile.setText(encryptFile.getAbsolutePath()); + } + if (decryptFile.toString().trim().isEmpty()) { + fileDecryptOutputFile.setText(""); + } else { + fileDecryptOutputFile.setText(decryptFile.getAbsolutePath()); + } + enDecryptInputOutputFiles.replace(label, change); + } + + /** + * <p>Deletes an entry for en- / decryption. + * Get called if the user presses 'del' or delete the entry in the en- / decryption box via the right click tooltip</p> + * + * @see Controller#fileEnDecryptDeleteEntry(Label) + */ + private void fileEnDecryptDeleteEntry() { + fileEnDecryptDeleteEntry(choosedLabel); + } + + /** + * <p>Deletes an entry for en- / decryption. + * Get called if the user presses 'del' or delete the entry in the en- / decryption box via the right click tooltip</p> + * + * @param label that should be deleted + */ + private void fileEnDecryptDeleteEntry(Label label) { + enDecryptInputOutputFiles.remove(label); + if (fileEnDecryptInputFiles.getChildren().size() - 1 >= 1) { + for (int i = 0; i < fileEnDecryptInputFiles.getChildren().size(); i++) { + if (fileEnDecryptInputFiles.getChildren().get(i) == label) { + fileEnDecryptInputFiles.getChildren().remove(label); + if (label == choosedLabel) { + try { + choosedLabel = (Label) fileEnDecryptInputFiles.getChildren().get(i - 1); + choosedLabelType = "ENDECRYPT"; + fileOutputFilesChangeText(choosedLabel, null, null); + fileEnDecryptSelected(choosedLabel); + } catch (ArrayIndexOutOfBoundsException e) { + e.printStackTrace(); + fileOutputFileChangeDest.setDisable(true); + getChoosedLabelOutputFileFolder.setDisable(true); + fileEncryptOutputFile.setEditable(false); + fileDecryptOutputFile.setEditable(false); + fileOutputFilesChangeText(choosedLabel, "", ""); + choosedLabel = null; + choosedLabelType = null; + } + break; + } + } + } + } else { + fileEnDecryptInputFiles.getChildren().remove(label); + fileOutputFileChangeDest.setDisable(true); + getChoosedLabelOutputFileFolder.setDisable(true); + fileEncryptOutputFile.setEditable(false); + fileDecryptOutputFile.setEditable(false); + if (label == choosedLabel) { + fileOutputFilesChangeText(choosedLabel, "", ""); + choosedLabel = null; + choosedLabelType = null; + } + } + } + + /** + * <p>Changes the highlight of the clicked item in the en- / decryption box. + * Get called if the user click an non-highlighted item in the en- / decryption box</p> + * + * @param changeLabel is the label that the user has clicked + */ + private void fileEnDecryptSelected(Label changeLabel) { + if (changeLabel != null) { + fileDeleteSelected(null); + enDecryptInputOutputFiles.keySet().forEach(label -> label.setStyle(null)); + changeLabel.setStyle("-fx-background-color: lightblue; -fx-border-color: #292929"); + fileDecryptOutputFile.setEditable(true); + fileEncryptOutputFile.setEditable(true); + fileOutputFileChangeDest.setDisable(false); + getChoosedLabelOutputFileFolder.setDisable(false); + choosedLabel = changeLabel; + choosedLabelType = "ENDECRYPT"; + } else { + enDecryptInputOutputFiles.keySet().forEach(label -> label.setStyle(null)); + fileDecryptOutputFile.setEditable(false); + fileEncryptOutputFile.setEditable(false); + fileOutputFileChangeDest.setDisable(true); + getChoosedLabelOutputFileFolder.setDisable(true); + } + } + + /** + * <p>Opens a file chooser GUI where the user can select the files that should be en- / decrypted. + * Get called if the 'Choose files...' in the file en- / decrypt section button is pressed</p> + */ + public void fileEnDecryptChoose() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Choose files"); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("All files", "*.*")); + List<File> files = fileChooser.showOpenMultipleDialog(rootWindow.getScene().getWindow()); + try { + if (files.size() >= 1) { + files.forEach(this::fileEnDecryptAddFile); + } + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + + /** + * <p>Get called if user drags a (normal or internet) file over the en- / decrypt file box</p> + * + * @param event source + */ + public void onFileEnDecryptDragOver(DragEvent event) { + Dragboard dragboard = event.getDragboard(); + if (event.getGestureSource() != fileEnDecryptInputFiles) { + if (dragboard.hasFiles()) { + if (dragboard.getFiles().size() == 1 && dragboard.getFiles().get(0).isDirectory()) { + return; + } else { + event.acceptTransferModes(TransferMode.COPY_OR_MOVE); + } + } else if (dragboard.hasUrl()) { + String url = dragboard.getUrl(); + String urlFilename = dragboard.getUrl().split("/")[dragboard.getUrl().split("/").length - 1]; + if (url.startsWith("data:")) { + try { + final int dataStartIndex = url.indexOf(",") + 1; + final String data = url.substring(dataStartIndex); + java.util.Base64.getDecoder().decode(data); + event.acceptTransferModes(TransferMode.COPY_OR_MOVE); + } catch (Exception e) { + e.printStackTrace(); + } + } else if (urlFilename.contains(".") && !Utils.hasAnyCharacter("\\/:*?|<>\"", urlFilename)) { + try { + new URL(url); + event.acceptTransferModes(TransferMode.COPY_OR_MOVE); + } catch (Exception e) { + e.printStackTrace(); + return; + } + } else { + event.acceptTransferModes(TransferMode.COPY_OR_MOVE); + } + } + } + } + + /** + * <p>Get called if the user drops the dragged (normal or internet) file over the en- / decrypt file box</p> + * + * @param event source + * @throws URISyntaxException + */ + public void onFileEnDecryptDragNDrop(DragEvent event) throws URISyntaxException { + Dragboard dragboard = event.getDragboard(); + if (dragboard.hasFiles()) { + dragboard.getFiles().forEach(file -> { + if (file.isFile()) { + fileEnDecryptAddFile(file); + } + }); + } else if (dragboard.hasUrl()) { + String url = dragboard.getUrl(); + String urlFilename = dragboard.getUrl().split("/")[dragboard.getUrl().split("/").length - 1]; + if (url.startsWith("data:")) { + fileEnDecryptAddInternetFile(url, DATAFILEURL); + } else if (urlFilename.contains(".") && !Utils.hasAnyCharacter("\\/:*?|<>\"", urlFilename)) { + fileEnDecryptAddInternetFile(url, FILEFILEURL); + } else { + fileEnDecryptAddInternetFile(url, NONSPECIFICFILEURL); + } + } + } + + /** + * <p>If the user presses Ctrl + V: Adds the last object in clipboard (if file) for en- / decryption. + * Get called if the user presses a key while selected file en- / decryption box</p> + * + * @param event source + * @throws URISyntaxException + */ + public void onFileEnDecryptPaste(KeyEvent event) throws URISyntaxException { + if (paste.match(event)) { + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + Transferable transferable = clipboard.getContents(null); + try { + if (transferable != null) { + if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { + Object objectFileList = transferable.getTransferData(DataFlavor.javaFileListFlavor); + List files = (List) objectFileList; + files.forEach(o -> fileEnDecryptAddFile((File) o)); + } else if (transferable.isDataFlavorSupported(DataFlavor.imageFlavor)) { + Object objectImage = transferable.getTransferData(DataFlavor.imageFlavor); + fileEnDecryptAddClipboardImage((BufferedImage) objectImage); + } + } + } catch (UnsupportedFlavorException | IOException e) { + e.printStackTrace(); + } + } + } + + /** + * <p>Encrypt all files given files. + * Get called if file 'Encrypt' button is pressed</p> + */ + public void fileEncryptButton() { + final byte[] salt; + if (!fileEnDecryptSaltEntry.getText().isEmpty()) { + salt = fileEnDecryptSaltEntry.getText().getBytes(StandardCharsets.UTF_8); + } else { + salt = new byte[16]; + } + if (!enDecryptInputOutputFiles.isEmpty()) { + removeFileFromFileBox.setDisable(true); + limitNumberOfThreads.setDisable(true); + for(Map.Entry<Label, ArrayList<File>> entry: enDecryptInputOutputFiles.entrySet()) { + Thread thread = new Thread(() -> { + addFileEnDecryptThread(Thread.currentThread()); + if (limitNumberOfThreads.isSelected()) { + while (totalThreads.get() >= Runtime.getRuntime().availableProcessors()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().stop(); + } + } + } + totalThreads.getAndIncrement(); + Label inputFileLabel = entry.getKey(); + ArrayList<File> outputFileList = entry.getValue(); + EnDecrypt.AES fileEncrypt = new EnDecrypt.AES(fileEnDecryptKeyEntry.getText(), salt); + if (enDecryptInputOutputInternetFiles.containsKey(inputFileLabel)) { + ArrayList<Object> fileSpecs = enDecryptInputOutputInternetFiles.get(inputFileLabel); + int urlType = (int) fileSpecs.get(0); + String url = (String) fileSpecs.get(1); + try { + if (urlType == FILEFILEURL) { + URLConnection openURL = new URL(url).openConnection(); + openURL.addRequestProperty("User-Agent", "Mozilla/5.0"); + fileEncrypt.encryptFileLineByLine(openURL.getInputStream(), new FileOutputStream((File) fileSpecs.get(2))); + } else if (urlType == DATAFILEURL) { + final int dataStartIndex = url.indexOf(",") + 1; + final String data = url.substring(dataStartIndex); + byte[] decoded = java.util.Base64.getDecoder().decode(data); + fileEncrypt.encryptFileAllInOne(decoded, new FileOutputStream((File) fileSpecs.get(2))); + } else if (urlType == NONSPECIFICFILEURL) { + URLConnection openURL = new URL(url).openConnection(); + openURL.addRequestProperty("User-Agent", "Mozilla/5.0"); + fileEncrypt.encryptFileLineByLine(openURL.getInputStream(), new FileOutputStream((File) fileSpecs.get(2))); + } + } catch (FileNotFoundException | InvalidKeySpecException | NoSuchAlgorithmException | MalformedURLException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + Platform.runLater(() -> errorAlert("IO Exception occurred", e.getMessage())); + } + } else if (enDecryptInputOutputClipboardImages.containsKey(inputFileLabel)) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + BufferedImage bufferedImage = enDecryptInputOutputClipboardImages.get(inputFileLabel); + try { + ImageIO.write(bufferedImage, "png", byteArrayOutputStream); + fileEncrypt.encryptFileAllInOne(byteArrayOutputStream.toByteArray(), new FileOutputStream(outputFileList.get(0).getAbsoluteFile())); + } catch (IOException e) { + e.printStackTrace(); + Platform.runLater(() -> errorAlert("IO Exception occurred", e.getMessage())); + } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | BadPaddingException | InvalidKeySpecException | IllegalBlockSizeException e) { + e.printStackTrace(); + } + } else { + try { + fileEncrypt.encryptFileLineByLine(inputFileLabel.getText(), outputFileList.get(0).getAbsolutePath()); + } catch (NoSuchPaddingException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + Platform.runLater(() -> errorAlert("IO Exception occurred", e.getMessage())); + } + } + if (removeFileFromFileBox.isSelected()) { + Platform.runLater(() -> fileEnDecryptDeleteEntry(entry.getKey())); + } + if ((getFileEnDecryptThreadsSize() - 1) <= 0) { + fileEnDecryptLoading = false; + Platform.runLater(() -> { + fileEnDecryptLoadingImage.setImage(null); + removeFileFromFileBox.setDisable(false); + limitNumberOfThreads.setDisable(false); + }); + } + removeFileEnDecryptThread(Thread.currentThread()); + totalThreads.getAndDecrement(); + }); + thread.setDaemon(false); + thread.start(); + if (!fileEnDecryptLoading) { + fileEnDecryptLoadingImage.setImage(loadingImage); + } + fileEnDecryptLoading = true; + } + } + } + + /** + * <p>Decrypt all files given files. + * Get called if file 'Decrypt' button is pressed</p> + */ + public void fileDecryptButton() { + final byte[] salt; + if (!fileEnDecryptSaltEntry.getText().isEmpty()) { + salt = fileEnDecryptSaltEntry.getText().getBytes(StandardCharsets.UTF_8); + } else { + salt = new byte[16]; + } + if (!enDecryptInputOutputFiles.isEmpty()) { + removeFileFromFileBox.setDisable(true); + limitNumberOfThreads.setDisable(true); + for(Map.Entry<Label, ArrayList<File>> entry: enDecryptInputOutputFiles.entrySet()) { + Thread thread = new Thread(() -> { + addFileEnDecryptThread(Thread.currentThread()); + if (limitNumberOfThreads.isSelected()) { + while (totalThreads.get() >= Runtime.getRuntime().availableProcessors()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().stop(); + } + } + } + totalThreads.getAndIncrement(); + Label inputFileLabel = entry.getKey(); + ArrayList<File> outputFileList = entry.getValue(); + EnDecrypt.AES fileDecrypt = new EnDecrypt.AES(fileEnDecryptKeyEntry.getText(), salt); + if (enDecryptInputOutputInternetFiles.containsKey(entry.getKey())) { + ArrayList<Object> imageSpecs = enDecryptInputOutputInternetFiles.get(entry.getKey()); + int urlType = (int) imageSpecs.get(0); + String url = (String) imageSpecs.get(1); + try { + if (urlType == FILEFILEURL) { + URLConnection openURL = new URL(url).openConnection(); + openURL.addRequestProperty("User-Agent", "Mozilla/5.0"); + fileDecrypt.decryptFileLineByLine(openURL.getInputStream(), new FileOutputStream((File) imageSpecs.get(2))); + } else if (urlType == DATAFILEURL) { + final int dataStartIndex = url.indexOf(",") + 1; + final String data = url.substring(dataStartIndex); + byte[] decoded = java.util.Base64.getDecoder().decode(data); + fileDecrypt.decryptFileAllInOne(decoded, new FileOutputStream((File) imageSpecs.get(2))); + } else if (urlType == NONSPECIFICFILEURL) { + URLConnection openURL = new URL(url).openConnection(); + openURL.addRequestProperty("User-Agent", "Mozilla/5.0"); + fileDecrypt.decryptFileLineByLine(openURL.getInputStream(), new FileOutputStream((File) imageSpecs.get(2))); + } + } catch (FileNotFoundException | InvalidKeySpecException | NoSuchAlgorithmException | MalformedURLException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + Platform.runLater(() -> errorAlert("IO Exception occurred", e.getMessage())); + } + } else if (enDecryptInputOutputClipboardImages.containsKey(inputFileLabel)) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + BufferedImage bufferedImage = enDecryptInputOutputClipboardImages.get(inputFileLabel); + try { + ImageIO.write(bufferedImage, "png", byteArrayOutputStream); + fileDecrypt.decryptFileAllInOne(byteArrayOutputStream.toByteArray(), new FileOutputStream(outputFileList.get(1).getAbsolutePath())); + } catch (IOException e) { + e.printStackTrace(); + Platform.runLater(() -> errorAlert("IO Exception occurred", e.getMessage())); + } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | BadPaddingException | InvalidKeySpecException | IllegalBlockSizeException e) { + e.printStackTrace(); + } + } else { + try { + fileDecrypt.decryptFileLineByLine(inputFileLabel.getText(), outputFileList.get(1).getAbsolutePath()); + } catch (NoSuchPaddingException | InvalidKeySpecException | InvalidKeyException | NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + Platform.runLater(() -> errorAlert("IO Exception occurred", e.getMessage())); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + Platform.runLater(() -> errorAlert("Wrong text for encryption is given", e.getMessage())); + } + } + if (removeFileFromFileBox.isSelected()) { + Platform.runLater(() -> fileEnDecryptDeleteEntry(entry.getKey())); + } + if ((getFileEnDecryptThreadsSize() - 1) <= 0) { + fileEnDecryptLoading = false; + Platform.runLater(() -> { + fileEnDecryptLoadingImage.setImage(null); + removeFileFromFileBox.setDisable(false); + limitNumberOfThreads.setDisable(false); + }); + } + removeFileEnDecryptThread(Thread.currentThread()); + totalThreads.getAndDecrement(); + }); + thread.setDaemon(false); + thread.start(); + if (!fileEnDecryptLoading) { + fileEnDecryptLoadingImage.setImage(loadingImage); + } + fileEnDecryptLoading = true; + } + } + } + + /** + * <p>Cancels the file en- / decryption. + * Get called if the file en- / decrypt 'Cancel' button is pressed</p> + */ + public void fileEnDecryptCancelButton() { + for (Iterator<Thread> iterator = getFileEnDecryptThreads().iterator(); iterator.hasNext();) { + Thread thread = iterator.next(); + while (thread.isAlive() && !thread.isInterrupted()) { + thread.stop(); + thread.interrupt(); + } + iterator.remove(); + } + fileEnDecryptLoading = false; + fileEnDecryptLoadingImage.setImage(null); + removeFileFromFileBox.setDisable(false); + limitNumberOfThreads.setDisable(false); + } + + //-----fileDelete-----// + + /** + * <p>Synchronized method to get the list of threads which delete files</p> + * + * @return list of threads which delete files + */ + private synchronized List<Thread> getFileDeleteThreads() { + return fileDeleteThreads; + } + + /** + * <p>Synchronized method to get the number of threads which delete files</p> + * + * @return number of threads which delete files + */ + private synchronized int getFileDeleteThreadsSize() { + return fileDeleteThreads.size(); + } + + /** + * <p>Synchronized method to add a thread to the file delete list of current running file delete threads</p> + * + * @param thread that should be added + */ + private synchronized void addFileDeleteThread(Thread thread) { + fileDeleteThreads.add(thread); + } + + /** + * <p>Synchronized method to remove a thread from the file delete list of current file delete threads</p> + * + * @param thread that should be removed + */ + private synchronized void removeFileDeleteThread(Thread thread) { + fileDeleteThreads.remove(thread); + } + + /** + * <p>Adds a file that should be deleted</p> + * + * @param file that should be added + */ + private void fileDeleteAddFile(File file) { + for (File f: deleteInputFiles.values()) { + if (f.getAbsolutePath().equals(file.getAbsolutePath())) { + return; + } + } + Label newLabel = new Label(file.getAbsolutePath()); + newLabel.setOnKeyTyped(this::keyTypedTooltip); + newLabel.setOnMouseMoved(this::mouseOverEntryTooltip); + newLabel.setOnMouseExited(event -> mouseExitEntryTooltip()); + newLabel.setOnMouseClicked(event -> { + fileDeleteSelected(newLabel); + if (event.getButton() == MouseButton.SECONDARY) { + fileDeleteInputContextMenu.show(newLabel, event.getScreenX(), event.getScreenY()); + } + }); + fileDeleteInputFiles.getChildren().add(newLabel); + deleteInputFiles.put(newLabel, file.getAbsoluteFile()); + } + + /** + * <p>Changes the highlight of the clicked item in the file delete box. + * Get called if the user click an non-highlighted item in the file delete box</p> + * + * @param changeLabel is the label that the user has clicked + */ + private void fileDeleteSelected(Label changeLabel) { + if (changeLabel != null) { + fileEnDecryptSelected(null); + deleteInputFiles.keySet().forEach(label -> label.setStyle(null)); + changeLabel.setStyle("-fx-background-color: lightblue; -fx-border-color: #292929"); + choosedLabel = changeLabel; + choosedLabelType = "DELETE"; + } else { + deleteInputFiles.keySet().forEach(label -> label.setStyle(null)); + } + } + + /** + * <p>Deletes an entry for file delete. + * Get called if the user presses 'del' or delete the entry in the file delete box via the right click tooltip</p> + * + * @see Controller#fileEnDecryptDeleteEntry(Label) + */ + private void fileDeleteDeleteEntry() { + fileEnDecryptDeleteEntry(choosedLabel); + } + + /** + * <p>Deletes an entry for file delete. + * Get called if the user presses 'del' or delete the entry in the file delete box via the right click tooltip</p> + * + * @param label that should be deleted + */ + private void fileDeleteDeleteEntry(Label label) { + deleteInputFiles.remove(choosedLabel); + if (fileDeleteInputFiles.getChildren().size() - 1 >= 1) { + for (int i=0; i<fileDeleteInputFiles.getChildren().size(); i++) { + if (fileDeleteInputFiles.getChildren().get(i) == choosedLabel) { + fileDeleteInputFiles.getChildren().remove(choosedLabel); + if (label == choosedLabel) { + try { + choosedLabel = (Label) fileDeleteInputFiles.getChildren().get(i - 1); + choosedLabelType = "DELETE"; + fileDeleteSelected(choosedLabel); + } catch (ArrayIndexOutOfBoundsException e) { + choosedLabel = null; + choosedLabelType = "DELETE"; + } + break; + } + } + } + } else { + fileDeleteInputFiles.getChildren().remove(choosedLabel); + choosedLabel = null; + choosedLabelType = "DELETE"; + } + } + + /** + * <p>Opens a file chooser GUI where the user can select the files that should be en- / decrypted. + * Get called if the 'Choose files...' in the delete section button is pressed</p> + */ + public void fileDeleteChoose() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Choose files"); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("All Files", "*.*")); + List<File> files = fileChooser.showOpenMultipleDialog(rootWindow.getScene().getWindow()); + try { + if (files.size() >= 1) { + files.forEach(file -> fileDeleteAddFile(file)); + } + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + + /** + * <p>Get called if user drags a file over the delete file box</p> + * + * @param event source + */ + public void onFileDeleteDragOver(DragEvent event) { + Dragboard dragboard = event.getDragboard(); + if (event.getGestureSource() != fileDeleteInputFiles && dragboard.hasFiles()) { + if (dragboard.getFiles().size() == 1 && dragboard.getFiles().get(0).isDirectory()) { + return; + } else { + event.acceptTransferModes(TransferMode.COPY_OR_MOVE); + } + } + } + + /** + * <p>Get called if the user drops the dragged file over the delete file box</p> + * + * @param event source + */ + public void onFileDeleteDragNDrop(DragEvent event) { + Dragboard dragboard = event.getDragboard(); + if (dragboard.hasFiles()) { + dragboard.getFiles().forEach(file -> { + if (file.isFile()) { + fileDeleteAddFile(file); + } + }); + } + } + + /** + * <p>Delete all given files. + * Get called if 'Delete' button is pressed</p> + */ + public void fileDelete() { + if (!fileDeleteLoading && !deleteInputFiles.isEmpty()) { + fileDeleteLoadingImage.setImage(loadingImage); + } + Iterator<Map.Entry<Label, File>> deleteIterator = deleteInputFiles.entrySet().iterator(); + while(deleteIterator.hasNext()) { + Map.Entry<Label, File> map = deleteIterator.next(); + Label label = map.getKey(); + File file = map.getValue(); + Thread thread = new Thread(() -> { + addFileDeleteThread(Thread.currentThread()); + if (limitNumberOfThreads.isSelected()) { + while (totalThreads.get() >= Runtime.getRuntime().availableProcessors()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().stop(); + } + } + } + totalThreads.getAndIncrement(); + String deleteFile = file.getAbsolutePath(); + try { + SecureDelete.deleteFileLineByLine(deleteFile, Integer.parseInt(fileDeleteIterationsEntry.getText())); + } catch (NoSuchAlgorithmException | IOException e) { + e.printStackTrace(); + } + if ((getFileDeleteThreadsSize() - 1) <= 0) { + fileDeleteLoading = false; + Platform.runLater(() -> fileDeleteLoadingImage.setImage(null)); + } + if (label == choosedLabel) { + choosedLabel = null; + choosedLabelType = null; + } + Platform.runLater(() -> fileDeleteInputFiles.getChildren().remove(label)); + removeFileDeleteThread(Thread.currentThread()); + totalThreads.getAndDecrement(); + }); + thread.setDaemon(false); + thread.start(); + fileDeleteLoading = true; + } + } + + /** + * <p>Cancels the file en- / decryption. + * Get called if the file delete 'Cancel' button is pressed</p> + */ + public void fileDeleteCancelButton() { + for (Iterator<Thread> iterator = getFileDeleteThreads().iterator(); iterator.hasNext();) { + Thread thread = iterator.next(); + while (thread.isAlive() & !thread.isInterrupted()) { + thread.stop(); + thread.interrupt(); + } + iterator.remove(); + } + fileDeleteLoading = false; + fileDeleteLoadingImage.setImage(null); + } + + /** + * Called to initialize a controller after its root element has been + * completely processed. + * + * @param location + * The location used to resolve relative paths for the root object, or + * <tt>null</tt> if the location is not known. + * + * @param resources + * The resources used to localize the root object, or <tt>null</tt> if + * the root object was not localized. + */ + @Override + public void initialize(URL location, ResourceBundle resources) { + + //-----general-----// + + currentConfigSettings.put("encryptHash", configDefaultEncryptHash); + + currentConfigSettings.put("textKey", configDefaultTextKey); + currentConfigSettings.put("textSalt", configDefaultTextSalt); + currentConfigSettings.put("textAlgorithm", configDefaultTextAlgorithm); + + currentConfigSettings.put("fileEnDecryptKey", configDefaultFileEnDecryptKey); + currentConfigSettings.put("fileEnDecryptSalt", configDefaultFileEnDecryptSalt); + currentConfigSettings.put("fileEnDecryptAlgorithm", configDefaultFileEnDecryptAlgorithm); + + currentConfigSettings.put("fileDeleteIterations", String.valueOf(configDefaultFileDeleteIterations)); + + currentConfigSettings.put("fileOutputPath", configDefaultFileOutputPath); + currentConfigSettings.put("removeFromFileBox", String.valueOf(configDefaultRemoveFileFromFileBox)); + currentConfigSettings.put("limitNumberOfThreads", String.valueOf(configDefaultLimitNumberOfThreads)); + + textAlgorithms.add("AES"); + fileEnDecryptAlgorithms.add("AES"); + + menubar.setOnMouseDragged(event -> { + Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow(); + stage.setX(event.getScreenX() + menubarX); + stage.setY(event.getScreenY() + menubarY); + }); + menubar.setOnMousePressed(event -> { + Scene scene = ((Node) event.getSource()).getScene(); + menubarX = scene.getX() - event.getSceneX(); + menubarY = scene.getY() - event.getSceneY(); + }); + + rootWindow.setOnKeyReleased(event -> { + if (event.getCode() == KeyCode.DELETE && choosedLabelType != null) { + if (choosedLabelType.equals("ENDECRYPT")) { + fileEnDecryptDeleteEntry(); + } else if (choosedLabelType.equals("DELETE")) { + fileDeleteDeleteEntry(); + } + } + }); + + getChoosedLabelInputFileFolder.setOnAction(event -> { + Desktop desktop = Desktop.getDesktop(); + String filePath = choosedLabel.getText(); + try { + desktop.open(new File(filePath.substring(0, filePath.lastIndexOf(System.getProperty("file.separator"))))); + } catch (IOException e) { + errorAlert("An unexpected IO Exception occurred", e.getMessage()); + } + }); + + getChoosedLabelOutputFileFolder.setOnAction(event -> { + Desktop desktop; + String filePath; + if (enDecryptInputOutputFiles.containsKey(choosedLabel)) { + desktop = Desktop.getDesktop(); + filePath = enDecryptInputOutputFiles.get(choosedLabel).get(0).getAbsolutePath(); + } else { + return; + } + try { + desktop.open(new File(filePath.substring(0, filePath.lastIndexOf(System.getProperty("file.separator"))))); + } catch (IOException e) { + errorAlert("An unexpected IO Exception occurred", e.getMessage()); + } + }); + + setDefaultOutputPath.setOnAction(event -> { + DirectoryChooser directoryChooser = new DirectoryChooser(); + File directory = directoryChooser.showDialog(rootWindow.getScene().getWindow()); + try { + currentConfigSettings.replace("fileOutputPath", directory.getAbsolutePath()); + } catch (NullPointerException e) { + e.printStackTrace(); + } + }); + + settingsMenu.setOnShowing(event -> { + loadSettings.setDisable(!isConfig); + exportSettings.setDisable(!isConfig); + }); + + removeFileFromFileBox.setOnAction(event -> currentConfigSettings.replace("removeFromFileBox", String.valueOf(removeFileFromFileBox.isSelected()))); + limitNumberOfThreads.setOnAction(event -> currentConfigSettings.replace("limitNumberOfThreads", String.valueOf(limitNumberOfThreads.isSelected()))); + saveSettings.setOnAction(event -> { + try { + addSettingGUI(rootWindow.getScene().getWindow(), currentConfigSettings); + if (config.isFile()) { + isConfig = true; + } + } catch (IOException e) { + e.printStackTrace(); + } + }); + loadSettings.setOnAction(event -> { + try { + currentConfigSettings = (HashMap<String, String>) loadSettingsGUI(rootWindow.getScene().getWindow()).values().toArray()[0]; + System.out.println(currentConfigSettings); + textKeyEntry.setText(currentConfigSettings.get("textKey")); + textSaltEntry.setText(currentConfigSettings.get("textSalt")); + textAlgorithmBox.setValue(currentConfigSettings.get("textAlgorithm")); + + fileEnDecryptKeyEntry.setText(currentConfigSettings.get("fileEnDecryptKey")); + fileEnDecryptSaltEntry.setText(currentConfigSettings.get("fileEnDecryptSalt")); + fileEnDecryptAlgorithmBox.setValue(currentConfigSettings.get("fileEnDecryptAlgorithm")); + + fileDeleteIterationsEntry.setText(currentConfigSettings.get("fileDeleteIterations")); + + removeFileFromFileBox.setSelected(Boolean.parseBoolean(currentConfigSettings.get("removeFromFileBox"))); + limitNumberOfThreads.setSelected(Boolean.parseBoolean(currentConfigSettings.get("limitNumberOfThreads"))); + } catch (IOException e) { + e.printStackTrace(); + } catch (ArrayIndexOutOfBoundsException ex) { + try { + SecureDelete.deleteFileLineByLine(config, 5); + isConfig = false; + } catch (NoSuchAlgorithmException | IOException e) { + e.printStackTrace(); + } + } + }); + exportSettings.setOnAction(event -> { + try { + exportSettingsGUI(rootWindow.getScene().getWindow()); + } catch (IOException e) { + e.printStackTrace(); + errorAlert("IO Exception occurred", e.getMessage()); + } + }); + importSettings.setOnAction(event -> { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Import settings"); + fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("Config files", "*.config*", "*.xml"), new FileChooser.ExtensionFilter("All files", "*.*")); + File file = fileChooser.showOpenDialog(rootWindow.getScene().getWindow()); + if (file != null) { + if (isConfig) { + readUserSettings(file).forEach((Config::addSetting)); + } else { + writeConfig(readUserSettings(file)); + isConfig = true; + } + } + }); + + //-----text------// + + textAlgorithmBox.setItems(FXCollections.observableArrayList(textAlgorithms)); + textAlgorithmBox.setValue(textAlgorithms.get(0)); + + //-----fileEnDecrypt-----// + + fileEnDecryptAlgorithmBox.setItems(FXCollections.observableArrayList(fileEnDecryptAlgorithms)); + fileEnDecryptAlgorithmBox.setValue(fileEnDecryptAlgorithms.get(0)); + + MenuItem enDecryptRemove = new MenuItem(); + enDecryptRemove.setText("Remove"); + enDecryptRemove.setOnAction(removeEvent -> fileEnDecryptDeleteEntry()); + MenuItem enDecryptChangeDest = new MenuItem(); + enDecryptChangeDest.setText("Change output file"); + enDecryptChangeDest.setOnAction(outputFileChangeEvent -> { + FileChooser fileDestChooser = new FileChooser(); + fileDestChooser.setTitle("Choose or create new file"); + fileDestChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("All files", "*.*")); + File file = fileDestChooser.showSaveDialog(rootWindow.getScene().getWindow()); + if (file != null) { + for (Map.Entry<Label, ArrayList<File>> entry : enDecryptInputOutputFiles.entrySet()) { + if (entry.getKey().getText().equals(choosedLabel.getText())) { + ArrayList<File> changedFile = new ArrayList<>(); + changedFile.add(0, file); + changedFile.add(1, file); + enDecryptInputOutputFiles.replace(entry.getKey(), entry.getValue(), changedFile); + fileOutputFilesChangeText((Label) fileEnDecryptLabelEvent.getSource(), file.getAbsolutePath(), file.getAbsolutePath()); + break; + } + } + } + }); + fileEnDecryptInputContextMenu.getItems().addAll(enDecryptRemove, enDecryptChangeDest); + + ContextMenu fileEnDecryptInputFilesMenu = new ContextMenu(); + MenuItem enDecryptPaste = new MenuItem(); + enDecryptPaste.setText("Paste"); + enDecryptPaste.setOnAction(pasteEvent -> { + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + Transferable transferable = clipboard.getContents(null); + try { + if (transferable != null) { + if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { + Object objectFileList = transferable.getTransferData(DataFlavor.javaFileListFlavor); + List files = (List) objectFileList; + files.forEach(o -> fileEnDecryptAddFile((File) o)); + } else if (transferable.isDataFlavorSupported(DataFlavor.imageFlavor)) { + Object objectImage = transferable.getTransferData(DataFlavor.imageFlavor); + fileEnDecryptAddClipboardImage((BufferedImage) objectImage); + } + } + } catch (UnsupportedFlavorException | IOException | URISyntaxException e) { + e.printStackTrace(); + } + }); + fileEnDecryptInputFilesMenu.getItems().add(enDecryptPaste); + + fileEnDecryptInputFiles.setOnContextMenuRequested(event -> { + if (!fileEnDecryptInputContextMenu.isShowing()) { + fileEnDecryptInputFilesMenu.show(((VBox) event.getSource()).getParent().getScene().getWindow(), event.getScreenX(), event.getScreenY()); + } + }); + + fileOutputFileChangeDest.setOnAction(event -> { + FileChooser fileDestChooser = new FileChooser(); + fileDestChooser.setTitle("Choose or create new file"); + fileDestChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("All files", "*.*")); + File file = fileDestChooser.showSaveDialog(rootWindow.getScene().getWindow()); + if (file != null) { + for (Map.Entry<Label, ArrayList<File>> entry : enDecryptInputOutputFiles.entrySet()) { + if (entry.getKey().getText().equals(choosedLabel.getText())) { + ArrayList<File> changedFile = new ArrayList<>(); + changedFile.add(0, file); + changedFile.add(1, file); + enDecryptInputOutputFiles.replace(entry.getKey(), entry.getValue(), changedFile); + fileOutputFilesChangeText((Label) fileEnDecryptLabelEvent.getSource(), file.getAbsolutePath(), file.getAbsolutePath()); + break; + } + } + } + }); + + fileOutputFileChangeDest.setDisable(true); + getChoosedLabelOutputFileFolder.setDisable(true); + + fileEncryptOutputFile.textProperty().addListener((observable, oldValue, newValue) -> { + fileOutputFilesChangeText(choosedLabel, newValue, fileDecryptOutputFile.getText()); + }); + fileDecryptOutputFile.textProperty().addListener((observable, oldValue, newValue) -> { + fileOutputFilesChangeText(choosedLabel, fileEncryptOutputFile.getText(), newValue); + }); + + //-----fileDelete-----// + + MenuItem deleteRemove = new MenuItem(); + deleteRemove.setText("Remove"); + deleteRemove.setOnAction(removeEvent -> fileDeleteDeleteEntry()); + fileDeleteInputContextMenu.getItems().addAll(deleteRemove); + + ContextMenu fileDeleteInputFilesMenu = new ContextMenu(); + MenuItem deletePaste = new MenuItem(); + deletePaste.setText("Paste"); + deletePaste.setOnAction(pasteEvent -> { + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + Transferable transferable = clipboard.getContents(null); + try { + if (transferable != null && transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { + Object objectFileList = transferable.getTransferData(DataFlavor.javaFileListFlavor); + List files = (List) objectFileList; + files.forEach(o -> fileDeleteAddFile((File) o)); + } + } catch (UnsupportedFlavorException | IOException e) { + e.printStackTrace(); + } + }); + fileDeleteInputFilesMenu.getItems().add(deletePaste); + + fileDeleteInputFiles.setOnContextMenuRequested(event -> { + if (!fileDeleteInputContextMenu.isShowing()) { + fileDeleteInputFilesMenu.show(((VBox) event.getSource()).getParent().getScene().getWindow(), event.getScreenX(), event.getScreenY()); + } + }); + + fileDeleteIterationsEntry.textProperty().addListener((observable, oldValue, newValue) -> { + if (!newValue.matches("[0-9]*")) { + fileDeleteIterationsEntry.setText(oldValue); + } + }); + + Thread t = new Thread(() -> { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (isConfig) { + Platform.runLater(() -> { + try { + currentConfigSettings = (HashMap<String, String>) loadSettingsGUI(rootWindow.getScene().getWindow()).values().toArray()[0]; + System.out.println(currentConfigSettings); + textKeyEntry.setText(currentConfigSettings.get("textKey")); + textSaltEntry.setText(currentConfigSettings.get("textSalt")); + textAlgorithmBox.setValue(currentConfigSettings.get("textAlgorithm")); + + fileEnDecryptKeyEntry.setText(currentConfigSettings.get("fileEnDecryptKey")); + fileEnDecryptSaltEntry.setText(currentConfigSettings.get("fileEnDecryptSalt")); + fileEnDecryptAlgorithmBox.setValue(currentConfigSettings.get("fileEnDecryptAlgorithm")); + + fileDeleteIterationsEntry.setText(currentConfigSettings.get("fileDeleteIterations")); + + removeFileFromFileBox.setSelected(Boolean.parseBoolean(currentConfigSettings.get("removeFromFileBox"))); + limitNumberOfThreads.setSelected(Boolean.parseBoolean(currentConfigSettings.get("limitNumberOfThreads"))); + } catch (IOException e) { + e.printStackTrace(); + } catch (ArrayIndexOutOfBoundsException ex) { + try { + SecureDelete.deleteFileLineByLine(config, 5); + isConfig = false; + } catch (NoSuchAlgorithmException | IOException e) { + e.printStackTrace(); + } + } + }); + } + }); + t.start(); + } +} diff --git a/src/org/blueshard/cryptogx/EnDecrypt.java b/src/org/blueshard/cryptogx/EnDecrypt.java new file mode 100644 index 0000000..2a49c3b --- /dev/null +++ b/src/org/blueshard/cryptogx/EnDecrypt.java @@ -0,0 +1,531 @@ +package org.blueshard.cryptogx; + +import javax.crypto.*; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Arrays; +import java.util.Base64; + +public class EnDecrypt { + + public static class AES extends Thread { + + private int iterations = 1000; + private int keyLength = 256; + + private final String key; + private final byte[] salt; + + public AES(String key, byte[] salt) { + this.key = key; + this.salt = salt; + } + + public AES(String key, byte[] salt, int iterations) { + this.key = key; + this.salt = salt; + this.iterations = iterations; + } + + public AES(String key, byte[] salt, int iterations, int keyLength) { + this.key = key; + this.salt = salt; + this.iterations = iterations; + this.keyLength = keyLength; + } + + /** + * <p>Creates a secret key from given (plain text) key and salt</p> + * + * @param key from which a secret key should be created + * @param salt from which a secret key should be created + * @return the secret key + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + */ + public byte[] createSecretKey(String key, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException { + KeySpec keySpec = new PBEKeySpec(key.toCharArray(), salt, this.iterations, keyLength); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); + + return factory.generateSecret(keySpec).getEncoded(); + } + + /** + * <p>Writes {@param inputStream} to {@param outputStream}</p> + * + * @param inputStream from which is written + * @param outputStream to which is written + * @param buffer + * @throws IOException + */ + public static void writeLineByLine(InputStream inputStream, OutputStream outputStream, byte[] buffer) throws IOException { + int numOfBytesRead; + while ((numOfBytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, numOfBytesRead); + } + outputStream.close(); + inputStream.close(); + } + + /** + * <p>Encrypts a file randomly line by line</p> + * + * @see EnDecrypt.AES#encryptRandomLineByLine(InputStream, OutputStream, byte[]) + */ + public static void encryptFileRandomLineByLine(File inputFile, File outputFile, byte[] buffer) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException, InvalidAlgorithmParameterException { + encryptRandomLineByLine(new FileInputStream(inputFile), new FileOutputStream(outputFile), buffer); + } + + /** + * <p>Encrypts a {@link InputStream} randomly line by line</p> + * + * @param inputStream that should be encrypted + * @param outputStream to which the encrypted {@param inputStream} should be written to + * @param buffer + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws IOException + * @throws InvalidAlgorithmParameterException + */ + public static void encryptRandomLineByLine(InputStream inputStream, OutputStream outputStream, byte[] buffer) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException, InvalidAlgorithmParameterException { + KeyGenerator randomKey = KeyGenerator.getInstance("AES"); + Key secretKey = new SecretKeySpec(randomKey.generateKey().getEncoded(), "AES"); + + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + + CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher); + writeLineByLine(cipherInputStream, outputStream, buffer); + } + + /** + * <p>En- / decrypts the {@param inputFile}</p> + * + * @param cipherMode says if the file should be en- or decrypted + * @param inputFile that should be en- / decrypted + * @param outputFile to which the en- / decrypted {@param inputFile} should be written to + * @throws InvalidKeySpecException + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws IOException + * @throws BadPaddingException + * @throws IllegalBlockSizeException + */ + public void enDecryptFileAllInOne(int cipherMode, File inputFile, File outputFile) throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException, BadPaddingException, IllegalBlockSizeException { + Key secretKey = new SecretKeySpec(createSecretKey(key, salt), "AES"); + + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(cipherMode, secretKey); + + FileInputStream inputStream = new FileInputStream(inputFile); + byte[] inputBytes = new byte[(int) inputFile.length()]; + inputStream.read(inputBytes); + + byte[] outputBytes = cipher.doFinal(inputBytes); + + FileOutputStream outputStream = new FileOutputStream(outputFile); + outputStream.write(outputBytes); + + inputStream.close(); + outputStream.close(); + } + + /** + * <p>En- / decrypts the {@param inputBytes}</p> + * + * @param cipherMode says if the file should be en- or decrypted + * @param inputBytes that should be en- / decrypted + * @param outputStream to which the en- / decrypted {@param inputFile} should be written to + * @throws InvalidKeySpecException + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws IOException + * @throws BadPaddingException + * @throws IllegalBlockSizeException + */ + public void enDecryptFileAllInOne(int cipherMode, byte[] inputBytes, OutputStream outputStream) throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException, BadPaddingException, IllegalBlockSizeException { + Key secretKey = new SecretKeySpec(createSecretKey(key, salt), "AES"); + + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(cipherMode, secretKey); + + byte[] outputBytes = cipher.doFinal(inputBytes); + + outputStream.write(outputBytes); + + outputStream.close(); + } + + /** + * <p>En- / decrypts the {@param inputFile}</p> + * + * @param cipherMode says if the file should be en- or decrypted + * @param inputFile that should be en- / decrypted + * @param outputFile to which the en- / decrypted {@param inputFile} should be written to + * @param buffer + * @throws InvalidKeySpecException + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws IOException + * @throws InvalidAlgorithmParameterException + */ + public void enDecryptLineByLine(int cipherMode, File inputFile, File outputFile, byte[] buffer) throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException, InvalidAlgorithmParameterException { + Key secretKey = new SecretKeySpec(createSecretKey(key, salt), "AES"); + + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(cipherMode, secretKey); + + FileInputStream fileInputStream = new FileInputStream(inputFile); + FileOutputStream fileOutputStream = new FileOutputStream(outputFile); + + if (cipherMode == Cipher.ENCRYPT_MODE) { + CipherInputStream cipherInputStream = new CipherInputStream(fileInputStream, cipher); + writeLineByLine(cipherInputStream, fileOutputStream, buffer); + } else if (cipherMode == Cipher.DECRYPT_MODE) { + CipherOutputStream cipherOutputStream = new CipherOutputStream(fileOutputStream, cipher); + writeLineByLine(fileInputStream, cipherOutputStream, buffer); + } + } + + /** + * <p>En- / decrypts the {@param inputStream}</p> + * + * @param cipherMode says if the file should be en- or decrypted + * @param inputStream that should be en- / decrypted + * @param outputStream to which the en- / decrypted {@param inputFile} should be written to + * @param buffer + * @throws InvalidKeySpecException + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws IOException + * @throws InvalidAlgorithmParameterException + */ + public void enDecryptLineByLine(int cipherMode, InputStream inputStream, OutputStream outputStream, byte[] buffer) throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException, InvalidAlgorithmParameterException { + Key secretKey = new SecretKeySpec(createSecretKey(key, salt), "AES"); + + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(cipherMode, secretKey); + + if (cipherMode == Cipher.ENCRYPT_MODE) { + CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher); + writeLineByLine(cipherInputStream, outputStream, buffer); + } else if (cipherMode == Cipher.DECRYPT_MODE) { + CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher); + writeLineByLine(inputStream, cipherOutputStream, buffer); + } + } + + /** + * <p>Encrypt {@param bytes} randomly</p> + * + * @see EnDecrypt.AES#encryptRandom(byte[]) + */ + public static String encryptRandom(String string) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { + return encryptRandom(string.getBytes(StandardCharsets.UTF_8)); + } + + /** + * <p>Encrypt {@param bytes} randomly</p> + * + * @param bytes that should be encrypted + * @return the encrypted {@param bytes} as {@link String} + * @throws NoSuchPaddingException + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + * @throws BadPaddingException + * @throws IllegalBlockSizeException + */ + public static String encryptRandom(byte[] bytes) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { + KeyGenerator randomKey = KeyGenerator.getInstance("AES"); + Key secretKey = new SecretKeySpec(randomKey.generateKey().getEncoded(), "AES"); + + Cipher encryptRandomCipher = Cipher.getInstance("AES"); + encryptRandomCipher.init(Cipher.ENCRYPT_MODE, secretKey); + return Base64.getEncoder().encodeToString(encryptRandomCipher.doFinal(bytes)); + } + + /** + * <p>Encrypts a file randomly at once</p> + * + * @see EnDecrypt.AES#encryptFileRandomAllInOne(File, File) + */ + public static void encryptFileRandomAllInOne(String inputFilename, String outputFilename) throws NoSuchPaddingException, NoSuchAlgorithmException, IOException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException { + encryptFileRandomAllInOne(new File(inputFilename), new File(outputFilename)); + } + + /** + * <p>Encrypts a file randomly at once</p> + * + * @param inputFile that should be encrypted + * @param outputFile to which the encrypted file should be written to + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws IOException + * @throws BadPaddingException + * @throws IllegalBlockSizeException + */ + public static void encryptFileRandomAllInOne(File inputFile, File outputFile) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException, BadPaddingException, IllegalBlockSizeException { + KeyGenerator randomKey = KeyGenerator.getInstance("AES"); + Key secretKey = new SecretKeySpec(randomKey.generateKey().getEncoded(), "AES"); + + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + + FileInputStream inputStream = new FileInputStream(inputFile); + byte[] inputBytes = new byte[(int) inputFile.length()]; + inputStream.read(inputBytes); + + byte[] outputBytes = cipher.doFinal(inputBytes); + + FileOutputStream outputStream = new FileOutputStream(outputFile); + outputStream.write(outputBytes); + + inputStream.close(); + outputStream.close(); + } + + /** + * <p>Encrypts {@param inputFilename} randomly line by line and write it to {@param outputFilename}</p> + * + * @see EnDecrypt.AES#encryptRandomLineByLine(InputStream, OutputStream, byte[]) + */ + public static void encryptFileRandomLineByLine(String inputFilename, String outputFilename) throws InvalidKeyException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchPaddingException, IOException { + encryptRandomLineByLine(new FileInputStream(inputFilename), new FileOutputStream(outputFilename), new byte[64]); + } + + /** + * <p>Encrypts {@param inputFilename} randomly line by line and write it to {@param outputFilename}</p> + * + * @see EnDecrypt.AES#encryptRandomLineByLine(InputStream, OutputStream, byte[]) + */ + public static void encryptFileRandomLineByLine(String inputFilename, String outputFilename, byte[] buffer) throws InvalidKeyException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchPaddingException, IOException { + encryptRandomLineByLine(new FileInputStream(inputFilename), new FileOutputStream(outputFilename), buffer); + } + + /** + * <p>Decrypt encrypted {@param encryptedString}</p> + * + * @see EnDecrypt.AES#decrypt(byte[]) + */ + public String decrypt(String encryptedString) throws BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + Key secretKey = new SecretKeySpec(createSecretKey(key, salt), "AES"); + + Cipher decryptCipher = Cipher.getInstance("AES"); + decryptCipher.init(Cipher.DECRYPT_MODE, secretKey); + return new String(decryptCipher.doFinal(Base64.getDecoder().decode(encryptedString)), StandardCharsets.UTF_8); + } + + /** + * <p>Decrypt encrypted {@param bytes}</p> + * + * @param bytes that should be decrypted + * @return decrypted bytes + * @throws BadPaddingException + * @throws IllegalBlockSizeException + * @throws NoSuchPaddingException + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + * @throws InvalidKeyException + */ + public byte[] decrypt(byte[] bytes) throws BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + return decrypt(Arrays.toString(bytes)).getBytes(StandardCharsets.UTF_8); + } + + /** + * <p>Encrypt {@param bytes}</p> + * + * @see EnDecrypt.AES#encrypt(byte[]) + */ + public String encrypt(String string) throws BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + return Base64.getEncoder().encodeToString(encrypt(string.getBytes(StandardCharsets.UTF_8))); + } + + /** + * <p>Encrypt {@param bytes}</p> + * + * @param bytes that should be encrypted + * @return encrypted bytes + * @throws BadPaddingException + * @throws IllegalBlockSizeException + * @throws NoSuchPaddingException + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + * @throws InvalidKeyException + */ + public byte[] encrypt(byte[] bytes) throws BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + Key secretKey = new SecretKeySpec(createSecretKey(key, salt), "AES"); + + Cipher encryptCipher = Cipher.getInstance("AES"); + encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey); + return encryptCipher.doFinal(bytes); + } + + /** + * <p>Decrypt encrypted {@param inputFilename} to {@param outputFilename} at once</p> + * + * @param inputFilename that should be decrypted + * @param outputFilename to which the decrypted content should be written to + * @throws NoSuchPaddingException + * @throws NoSuchAlgorithmException + * @throws IOException + * @throws BadPaddingException + * @throws IllegalBlockSizeException + * @throws InvalidKeyException + * @throws InvalidKeySpecException + */ + public void decryptFileAllInOne(String inputFilename, String outputFilename) throws NoSuchPaddingException, NoSuchAlgorithmException, IOException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException { + enDecryptFileAllInOne(Cipher.DECRYPT_MODE, new File(inputFilename), new File(outputFilename)); + } + + /** + * <p>Decrypt encrypted {@param inputBytes} to {@param outputStream} at once</p> + * + * @param inputBytes that should be decrypted + * @param outputStream to which the decrypted content should be written to + * @throws NoSuchPaddingException + * @throws NoSuchAlgorithmException + * @throws IOException + * @throws BadPaddingException + * @throws IllegalBlockSizeException + * @throws InvalidKeyException + * @throws InvalidKeySpecException + */ + public void decryptFileAllInOne(byte[] inputBytes, OutputStream outputStream) throws NoSuchPaddingException, NoSuchAlgorithmException, IOException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException { + enDecryptFileAllInOne(Cipher.DECRYPT_MODE, inputBytes, outputStream); + } + + /** + * <p>Decrypt encrypted {@param inputFilename} to {@param outputFilename} line by line</p> + * + * @see EnDecrypt.AES#decryptFileLineByLine(InputStream, OutputStream, byte[]) + */ + public void decryptFileLineByLine(String inputFilename, String outputFilename) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, InvalidKeyException, InvalidKeySpecException { + enDecryptLineByLine(Cipher.DECRYPT_MODE, new File(inputFilename), new File(outputFilename), new byte[64]); + } + + /** + * <p>Decrypt encrypted {@param inputStream} to {@param outputStream} line by line</p> + * + * @see EnDecrypt.AES#decryptFileLineByLine(InputStream, OutputStream, byte[]) + */ + public void decryptFileLineByLine(InputStream inputStream, OutputStream outputStream) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, InvalidKeyException, InvalidKeySpecException { + enDecryptLineByLine(Cipher.DECRYPT_MODE, inputStream, outputStream, new byte[64]); + } + + /** + * <p>Decrypt encrypted {@param inputFilename} to {@param outputFilename} line by line</p> + * + * @see EnDecrypt.AES#decryptFileLineByLine(InputStream, OutputStream, byte[]) + */ + public void decryptFileLineByLine(String inputFilename, String outputFilename, byte[] buffer) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, InvalidKeyException, InvalidKeySpecException { + enDecryptLineByLine(Cipher.DECRYPT_MODE, new File(inputFilename), new File(outputFilename), buffer); + } + + /** + * <p>Decrypt encrypted {@param inputStream} to {@param outputStream} line by line</p> + * + * @param inputStream that should be decrypted + * @param outputStream to which the decrypted content should be written to + * @param buffer + * @throws NoSuchPaddingException + * @throws InvalidAlgorithmParameterException + * @throws NoSuchAlgorithmException + * @throws IOException + * @throws InvalidKeyException + * @throws InvalidKeySpecException + */ + public void decryptFileLineByLine(InputStream inputStream, OutputStream outputStream, byte[] buffer) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, InvalidKeyException, InvalidKeySpecException { + enDecryptLineByLine(Cipher.DECRYPT_MODE, inputStream, outputStream, buffer); + } + + /** + * <p>DEncrypt {@param inputFilename} to {@param outputFilename} at once</p> + * + * @param inputFilename that should be encrypt + * @param outputFilename to which the encrypted content should be written to + * @throws NoSuchPaddingException + * @throws NoSuchAlgorithmException + * @throws IOException + * @throws BadPaddingException + * @throws IllegalBlockSizeException + * @throws InvalidKeyException + * @throws InvalidKeySpecException + */ + public void encryptFileAllInOne(String inputFilename, String outputFilename) throws NoSuchPaddingException, NoSuchAlgorithmException, IOException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException { + enDecryptFileAllInOne(Cipher.ENCRYPT_MODE, new File(inputFilename), new File(outputFilename)); + } + + /** + * <p>Encrypt {@param inputBytes} to {@param outputStream} at once</p> + * + * @param inputBytes that should be encrypted + * @param outputStream to which the encrypted content should be written to + * @throws NoSuchPaddingException + * @throws NoSuchAlgorithmException + * @throws IOException + * @throws BadPaddingException + * @throws IllegalBlockSizeException + * @throws InvalidKeyException + * @throws InvalidKeySpecException + */ + public void encryptFileAllInOne(byte[] inputBytes, OutputStream outputStream) throws NoSuchPaddingException, NoSuchAlgorithmException, IOException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException { + enDecryptFileAllInOne(Cipher.ENCRYPT_MODE, inputBytes, outputStream); + } + + /** + * <p>Encrypt {@param inputFilename} to {@param outputFilename} line by line</p> + * + * @see EnDecrypt.AES#encryptFileLineByLine(InputStream, OutputStream, byte[]) + */ + public void encryptFileLineByLine(String inputFilename, String outputFilename) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, InvalidKeyException, InvalidKeySpecException { + enDecryptLineByLine(Cipher.ENCRYPT_MODE, new File(inputFilename), new File(outputFilename), new byte[64]); + } + + /** + * <p>Encrypt {@param inputStream} to {@param outputStream} line by line</p> + * + * @see EnDecrypt.AES#encryptFileLineByLine(InputStream, OutputStream, byte[]) + */ + public void encryptFileLineByLine(InputStream inputStream, OutputStream outputStream) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, InvalidKeyException, InvalidKeySpecException { + enDecryptLineByLine(Cipher.ENCRYPT_MODE, inputStream, outputStream, new byte[64]); + } + + /** + * <p>Encrypt {@param inputFilename} to {@param outputFilename} line by line</p> + * + * @see EnDecrypt.AES#encryptFileLineByLine(InputStream, OutputStream, byte[]) + */ + public void encryptFileLineByLine(String inputFilename, String outputFilename, byte[] buffer) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, InvalidKeyException, InvalidKeySpecException { + enDecryptLineByLine(Cipher.ENCRYPT_MODE, new File(inputFilename), new File(outputFilename), buffer); + } + + /** + * <p>Encrypt {@param inputStream} to {@param outputStream} line by line</p> + * + * @param inputStream that should be encrypted + * @param outputStream to which the encrypted {@param inputStream} should be written to + * @param buffer + * @throws NoSuchPaddingException + * @throws InvalidAlgorithmParameterException + * @throws NoSuchAlgorithmException + * @throws IOException + * @throws InvalidKeyException + * @throws InvalidKeySpecException + */ + public void encryptFileLineByLine(InputStream inputStream, OutputStream outputStream, byte[] buffer) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, InvalidKeyException, InvalidKeySpecException { + enDecryptLineByLine(Cipher.ENCRYPT_MODE, inputStream, outputStream, buffer); + } + + } +} diff --git a/src/org/blueshard/cryptogx/Main.java b/src/org/blueshard/cryptogx/Main.java new file mode 100644 index 0000000..65a9fe7 --- /dev/null +++ b/src/org/blueshard/cryptogx/Main.java @@ -0,0 +1,269 @@ +/** + * + * @author blueShard + * @version 1.11.0 + */ + +package org.blueshard.cryptogx; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.stage.Screen; +import javafx.stage.Stage; +import javafx.stage.StageStyle; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.swing.*; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +public class Main extends Application { + + protected static final int NON_PORTABLE = 414729643; + protected static final int PORTABLE = 245714766; + + protected static final int TYPE = PORTABLE; + + protected final static String configDefaultName = ""; + protected final static String configDefaultEncryptHash = ""; + protected final static String configDefaultTextKey = ""; + protected final static String configDefaultTextSalt = ""; + protected final static String configDefaultTextAlgorithm = "AES"; + protected final static String configDefaultFileEnDecryptKey = ""; + protected final static String configDefaultFileEnDecryptSalt = ""; + protected final static String configDefaultFileEnDecryptAlgorithm = "AES"; + protected final static int configDefaultFileDeleteIterations = 5; + protected final static String configDefaultFileOutputPath = ""; + protected final static boolean configDefaultRemoveFileFromFileBox = false; + protected final static boolean configDefaultLimitNumberOfThreads = true; + + protected static ArrayList<String> textAlgorithms = new ArrayList<>(); + protected static ArrayList<String> fileEnDecryptAlgorithms = new ArrayList<>(); + + private static Stage mainStage; + private double rootWindowX, rootWindowY; + protected static File config; + protected static boolean isConfig; + + /** + * <p>Start the GUI</p> + * + * @param primaryStage of the GUI + * @throws IOException if issues with loading 'mainGUI.fxml' + */ + @Override + public void start(Stage primaryStage) throws IOException { + Thread.setDefaultUncaughtExceptionHandler(Main::exceptionAlert); + + mainStage = primaryStage; + + Parent root = FXMLLoader.load(getClass().getResource("resources/mainGUI.fxml")); + primaryStage.initStyle(StageStyle.UNDECORATED); + primaryStage.setResizable(false); + primaryStage.setTitle("cryptoGX"); + primaryStage.getIcons().add(new Image(getClass().getResource("resources/cryptoGX.png").toExternalForm())); + Scene scene = new Scene(root, 900, 470); + + scene.setOnMouseDragged(event -> { + primaryStage.setX(event.getScreenX() + rootWindowX); + primaryStage.setY(event.getScreenY() + rootWindowY); + }); + scene.setOnMousePressed(event -> { + rootWindowX = scene.getX() - event.getSceneX(); + rootWindowY = scene.getY() - event.getSceneY(); + }); + + primaryStage.setScene(scene); + primaryStage.show(); + } + + /** + * <p>Enter method for the application. + * Can also be used to en- / decrypt text and files or secure delete files without starting GUI</p> + * + * @param args from the command line + * @return status + * @throws BadPaddingException + * @throws NoSuchAlgorithmException if wrong algorithm is given (command line) + * @throws IllegalBlockSizeException if wrong size for key is given (command line) + * @throws NoSuchPaddingException + * @throws InvalidKeyException if invalid key is given (command line) + * @throws InvalidKeySpecException + * @throws IOException if files cannot be en- / decrypted or deleted correctly (command line) + * @throws InvalidAlgorithmParameterException if wrong algorithm parameters are given (command line) + */ + public static void main(String[] args) throws BadPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, NoSuchPaddingException, InvalidKeyException, InvalidKeySpecException, IOException, InvalidAlgorithmParameterException { + if (Main.TYPE == Main.NON_PORTABLE) { + if (System.getProperty("os.name").toLowerCase().startsWith("windows")) { + config = new File("C:\\Users\\" + System.getProperty("user.name") + "\\AppData\\Roaming\\cryptoGX\\cryptoGX.config"); + } else { + config = new File("cryptoGX.config"); + } + } else { + config = new File("cryptoGX.config"); + } + isConfig = config.isFile(); + if (args.length == 0) { + String version = Runtime.class.getPackage().getImplementationVersion(); + if (version.startsWith("1.")) { + if (Integer.parseInt(version.substring(2, 3)) < 8) { + System.out.println("1"); + JOptionPane.showMessageDialog(null, "Please use java 1.8.0_240 to java 10.*", "ERROR", JOptionPane.ERROR_MESSAGE); + } else if (Integer.parseInt(version.substring(6, 9)) < 240) { + JOptionPane.showMessageDialog(null, "Please use java 1.8.0_240 to java 10.*", "ERROR", JOptionPane.ERROR_MESSAGE); + } + } else if (Integer.parseInt(version.substring(0, 2)) > 10) { + JOptionPane.showMessageDialog(null, "Please use java 1.8.0_240 to java 10.*", "ERROR", JOptionPane.ERROR_MESSAGE); + } else { + JOptionPane.showMessageDialog(null, "Please use java 1.8.0_240 to java 10.*", "ERROR", JOptionPane.ERROR_MESSAGE); + } + launch(args); + } else { + args[0] = args[0].replace("-", ""); + if (args[0].toLowerCase().equals("help") || args[0].toUpperCase().equals("H")) { + System.out.println("Usage AES: \n\n" + + " Text en- / decryption\n" + + " encrypt: <cryptoGX jar file> AES <key> <salt> encrypt <string>\n" + + " decrypt: <cryptoGX jar file> AES <key> <salt> decrypt <encrypted string>\n\n" + + " File en- / decryption\n" + + " encrypt: <cryptoGX jar file> AES <key> <salt> encrypt <path of file to encrypt> <encrypted file dest>\n" + + " decrypt: <cryptoGX jar file> AES <key> <salt> decrypt <encrypted file path> <decrypted file dest>\n\n" + + "File secure delete: <iterations> <path of file to delete>"); + } else if (args[0].toLowerCase().equals("delete")) { + if (args.length > 3) { + System.err.println("To many arguments were given, expected 3"); + } else if (args.length < 3) { + System.err.println("To few arguments were given, expected 3"); + } + try { + SecureDelete.deleteFileLineByLine(args[2], Integer.parseInt(args[1])); + } catch (NumberFormatException e) { + System.err.println(args[1] + " must be a number\n Error: " + e.getMessage()); + } + } else if (args[0].toLowerCase().equals("aes")) { + if (args.length < 4) { + System.err.println("To few arguments were given"); + System.exit(1); + } + EnDecrypt.AES aes; + if (args[2].isEmpty()) { + aes = new EnDecrypt.AES(args[1], new byte[16]); + } else { + aes = new EnDecrypt.AES(args[1], args[2].getBytes(StandardCharsets.UTF_8)); + } + String type = args[3].toLowerCase(); + if (type.equals("encrypt")) { + System.out.println(aes.encrypt(args[4])); + } else if (type.equals("decrypt")) { + System.out.println(aes.decrypt(args[4])); + } else if (type.equals("fileencrypt") || type.equals("encryptfile")) { + aes.encryptFileLineByLine(args[4], args[5]); + } else if (type.equals("filedecrypt") ||type.equals("decryptfile")) { + aes.decryptFileLineByLine(args[4], args[5]); + } + } + } + System.exit(0); + } + + /** + * <p>"Catch" all uncatched exceptions and opens an alert window</p> + * + * @param thread which called this method + * @param throwable of the thread which called the method + */ + private static void exceptionAlert(Thread thread, Throwable throwable) { + throwable.printStackTrace(); + + AtomicReference<Double> exceptionAlertX = new AtomicReference<>(Screen.getPrimary().getBounds().getMaxX() / 2); + AtomicReference<Double> exceptionAlertY = new AtomicReference<>(Screen.getPrimary().getBounds().getMaxY() / 2); + + Alert enDecryptError = new Alert(Alert.AlertType.ERROR, "Error: " + throwable, ButtonType.OK); + enDecryptError.initStyle(StageStyle.UNDECORATED); + enDecryptError.setTitle("Error"); + ((Stage) enDecryptError.getDialogPane().getScene().getWindow()).getIcons().add(new Image(Main.class.getResource("resources/cryptoGX.png").toExternalForm())); + + Scene window = enDecryptError.getDialogPane().getScene(); + + window.setOnMouseDragged(dragEvent -> { + enDecryptError.setX(dragEvent.getScreenX() + exceptionAlertX.get()); + enDecryptError.setY(dragEvent.getScreenY() + exceptionAlertY.get()); + }); + window.setOnMousePressed(pressEvent -> { + exceptionAlertX.set(window.getX() - pressEvent.getSceneX()); + exceptionAlertY.set(window.getY() - pressEvent.getSceneY()); + }); + + enDecryptError.show(); + } + + /** + * <p>Shows an error alert window</p> + * + * @param message which will the alert show + * @param error which will show after the message + */ + protected static void errorAlert(String message, String error) { + AtomicReference<Double> alertX = new AtomicReference<>(Screen.getPrimary().getBounds().getMaxX() / 2); + AtomicReference<Double> alertY = new AtomicReference<>(Screen.getPrimary().getBounds().getMaxY() / 2); + + Alert enDecryptError = new Alert(Alert.AlertType.ERROR, message + + "\nError: " + error, ButtonType.OK); + enDecryptError.initStyle(StageStyle.UNDECORATED); + enDecryptError.setTitle("Error"); + ((Stage) enDecryptError.getDialogPane().getScene().getWindow()).getIcons().add(new Image(Main.class.getResource("resources/cryptoGX.png").toExternalForm())); + + Scene window = enDecryptError.getDialogPane().getScene(); + + window.setOnMouseDragged(dragEvent -> { + enDecryptError.setX(dragEvent.getScreenX() + alertX.get()); + enDecryptError.setY(dragEvent.getScreenY() + alertY.get()); + }); + window.setOnMousePressed(pressEvent -> { + alertX.set(window.getX() - pressEvent.getSceneX()); + alertY.set(window.getY() - pressEvent.getSceneY()); + }); + + enDecryptError.show(); + } + + /** + * <p>Shows an warning alert window</p> + * + * @param message that the alert window will show + */ + protected static void warningAlert(String message) { + AtomicReference<Double> alertX = new AtomicReference<>(Screen.getPrimary().getBounds().getMaxX() / 2); + AtomicReference<Double> alertY = new AtomicReference<>(Screen.getPrimary().getBounds().getMaxY() / 2); + + Alert enDecryptError = new Alert(Alert.AlertType.WARNING, message, ButtonType.OK); + enDecryptError.initStyle(StageStyle.UNDECORATED); + enDecryptError.setTitle("Error"); + ((Stage) enDecryptError.getDialogPane().getScene().getWindow()).getIcons().add(new Image(Main.class.getResource("resources/cryptoGX.png").toExternalForm())); + + Scene window = enDecryptError.getDialogPane().getScene(); + + window.setOnMouseDragged(dragEvent -> { + enDecryptError.setX(dragEvent.getScreenX() + alertX.get()); + enDecryptError.setY(dragEvent.getScreenY() + alertY.get()); + }); + window.setOnMousePressed(pressEvent -> { + alertX.set(window.getX() - pressEvent.getSceneX()); + alertY.set(window.getY() - pressEvent.getSceneY()); + }); + + enDecryptError.show(); + } +} diff --git a/src/org/blueshard/cryptogx/SecureDelete.java b/src/org/blueshard/cryptogx/SecureDelete.java new file mode 100644 index 0000000..6b3a49e --- /dev/null +++ b/src/org/blueshard/cryptogx/SecureDelete.java @@ -0,0 +1,196 @@ +package org.blueshard.cryptogx; + +import java.io.*; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; + +public class SecureDelete { + + /** + * <p>Overwrites the file {@param iterations} times at once with random bytes an delete it</p> + * + * @see SecureDelete#deleteFileAllInOne(File, int) + */ + public static boolean deleteFileAllInOne(String filename, int iterations) throws IOException, NoSuchAlgorithmException { + return deleteFileAllInOne(new File(filename), iterations); + } + + /** + * <p>Overwrites the file {@param iterations} times at once with random bytes and delete it</p> + * + * @param file that should be deleted + * @param iterations how many times the file should be overwritten before it gets deleted + * @return if the file could be deleted + * @throws IOException + * @throws NoSuchAlgorithmException + */ + public static boolean deleteFileAllInOne(File file, int iterations) throws IOException, NoSuchAlgorithmException { + long fileLength = file.length() + 1 ; + for (int i=0; i<iterations; i++) { + BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file)); + if (fileLength > 1000000000) { + int numOfByteArrays = (int) Math.ceil((double) fileLength / 1000000000); + for (int len=0; len<numOfByteArrays; len++) { + int newMaxFileSize = (int) fileLength / numOfByteArrays; + int newMinFileSize = 0; + byte[] randomBytes = new byte[new Random().nextInt(newMaxFileSize - newMinFileSize) + newMinFileSize]; + SecureRandom.getInstanceStrong().nextBytes(randomBytes); + bufferedOutputStream.write(randomBytes); + } + } else { + byte[] randomBytes = new byte[new Random().nextInt((int) fileLength)]; + SecureRandom.getInstanceStrong().nextBytes(randomBytes); + bufferedOutputStream.write(randomBytes); + } + bufferedOutputStream.flush(); + bufferedOutputStream.close(); + } + + return file.delete(); + } + + /** + * <p>Overwrites the file {@param iterations} times at once with random bytes (minimal size {@param minFileSize}; maximal size {@param maxFileSize}) and delete it</p> + * + * @see SecureDelete#deleteFileAllInOne(String, int, long, long) + */ + public static boolean deleteFileAllInOne(String filename, int iterations, long minFileSize, long maxFileSize) throws IOException, NoSuchAlgorithmException { + return deleteFileAllInOne(new File(filename), iterations, minFileSize, maxFileSize); + } + + /** + * <p>Overwrites the file {@param iterations} times at once with random bytes (minimal size {@param minFileSize}; maximal size {@param maxFileSize}) and delete it</p> + * + * @param file that should be deleted + * @param iterations how many times the file should be overwritten before it gets deleted + * @param minFileSize is the minimal file size for every {@param iterations} + * @param maxFileSize is the maximal file size for every {@param iterations} + * @return if the file could be deleted + * @throws IOException + * @throws NoSuchAlgorithmException + */ + public static boolean deleteFileAllInOne(File file, int iterations, long minFileSize, long maxFileSize) throws IOException, NoSuchAlgorithmException { + for (int i = 0; i < iterations; i++) { + BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file)); + if (maxFileSize > 1000000000) { + int numOfByteArrays = (int) Math.ceil((double) maxFileSize / 1000000000); + for (int len = 0; len < numOfByteArrays; len++) { + int newMaxFileSize = (int) maxFileSize / numOfByteArrays; + int newMinFileSize = 0; + if (minFileSize != 0) { + newMinFileSize = (int) minFileSize / numOfByteArrays; + } + byte[] randomBytes = new byte[new Random().nextInt(newMaxFileSize - newMinFileSize) + newMinFileSize]; + SecureRandom.getInstanceStrong().nextBytes(randomBytes); + bufferedOutputStream.write(randomBytes); + } + } else { + byte[] randomBytes = new byte[new Random().nextInt((int) maxFileSize - (int) minFileSize) + (int) minFileSize]; + SecureRandom.getInstanceStrong().nextBytes(randomBytes); + bufferedOutputStream.write(randomBytes); + } + bufferedOutputStream.flush(); + bufferedOutputStream.close(); + } + + return file.delete(); + } + + /** + * <p>Overwrites the file {@param iterations} times line by line with random bytes and delete it</p> + * + * @see SecureDelete#deleteFileLineByLine(File, int) + */ + public static boolean deleteFileLineByLine(String filename, int iterations) throws NoSuchAlgorithmException, IOException { + return deleteFileLineByLine(new File(filename), iterations); + } + + /** + * <p>Overwrites the file {@param iterations} times line by line with random bytes and delete it</p> + * + * @param file that should be deleted + * @param iterations how many times the file should be overwritten before it gets deleted + * @return if the file could be deleted + * @throws IOException + * @throws NoSuchAlgorithmException + */ + public static boolean deleteFileLineByLine(File file, int iterations) throws NoSuchAlgorithmException, IOException { + long fileLength = file.length() + 1 ; + for (int i=0; i<iterations; i++) { + BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file)); + if (fileLength > 1000000000) { + int numOfByteArrays = (int) Math.ceil((double) fileLength / 1000000000); + for (int len=0; len<numOfByteArrays; len++) { + int newMaxFileSize = (int) fileLength / numOfByteArrays; + int newMinFileSize = 0; + byte[] randomBytes = new byte[new Random().nextInt(newMaxFileSize - newMinFileSize) + newMinFileSize]; + SecureRandom.getInstanceStrong().nextBytes(randomBytes); + for (byte b: randomBytes) { + bufferedOutputStream.write(b); + } + } + } else { + byte[] randomBytes = new byte[new Random().nextInt((int) fileLength)]; + SecureRandom.getInstanceStrong().nextBytes(randomBytes); + for (byte b : randomBytes) { + bufferedOutputStream.write(b); + } + } + bufferedOutputStream.flush(); + bufferedOutputStream.close(); + } + + return file.delete(); + } + + /** + * <p>Overwrites the file {@param iterations} times line by line with random bytes (minimal size {@param minFileSize}; maximal size {@param maxFileSize}) and delete it</p> + */ + public static boolean deleteFileLineByLine(String filename, int iterations, long minFileSize, long maxFileSize) throws NoSuchAlgorithmException, IOException { + return deleteFileLineByLine(new File(filename), iterations, minFileSize, maxFileSize); + } + + /** + * <p>Overwrites the file {@param iterations} times line by line with random bytes (minimal size {@param minFileSize}; maximal size {@param maxFileSize}) and delete it</p> + * + * @param file that should be deleted + * @param iterations how many times the file should be overwritten before it gets deleted + * @param minFileSize is the minimal file size for every {@param iterations} + * @param maxFileSize is the maximal file size for every {@param iterations} + * @return if the file could be deleted + * @throws IOException + * @throws NoSuchAlgorithmException + */ + public static boolean deleteFileLineByLine(File file, int iterations, long minFileSize, long maxFileSize) throws NoSuchAlgorithmException, IOException { + for (int i=0; i<iterations; i++) { + BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file)); + if (maxFileSize > 1000000000) { + int numOfByteArrays = (int) Math.ceil((double) maxFileSize / 1000000000); + for (int len=0; len<numOfByteArrays; len++) { + int newMaxFileSize = (int) maxFileSize / numOfByteArrays; + int newMinFileSize = 0; + if (minFileSize != 0) { + newMinFileSize = (int) minFileSize / numOfByteArrays; + } + byte[] randomBytes = new byte[new Random().nextInt(newMaxFileSize - newMinFileSize) + newMinFileSize]; + SecureRandom.getInstanceStrong().nextBytes(randomBytes); + for (byte b: randomBytes) { + bufferedOutputStream.write(b); + } + } + } else { + byte[] randomBytes = new byte[new Random().nextInt((int) maxFileSize - (int) minFileSize) + (int) minFileSize]; + SecureRandom.getInstanceStrong().nextBytes(randomBytes); + for (byte b : randomBytes) { + bufferedOutputStream.write(b); + } + } + bufferedOutputStream.flush(); + bufferedOutputStream.close(); + } + + return file.delete(); + } + +} diff --git a/src/org/blueshard/cryptogx/Utils.java b/src/org/blueshard/cryptogx/Utils.java new file mode 100644 index 0000000..6d6f73e --- /dev/null +++ b/src/org/blueshard/cryptogx/Utils.java @@ -0,0 +1,21 @@ +package org.blueshard.cryptogx; + +public class Utils { + + /** + * <p>Checks if any character in {@param characters} appears in {@param string}</p> + * + * @param characters that should be searched in {@param string} + * @param string that should be searched for the characters + * @return if any character in {@param characters} appears in {@param string} + */ + public static boolean hasAnyCharacter(CharSequence characters, String string) { + for (char c: characters.toString().toCharArray()) { + if (string.indexOf(c) != -1) { + return true; + } + } + return false; + } + +} diff --git a/src/org/blueshard/cryptogx/resources/addSettingsGUI.fxml b/src/org/blueshard/cryptogx/resources/addSettingsGUI.fxml new file mode 100644 index 0000000..5d8b7d4 --- /dev/null +++ b/src/org/blueshard/cryptogx/resources/addSettingsGUI.fxml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.Accordion?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.CheckBox?> +<?import javafx.scene.control.ComboBox?> +<?import javafx.scene.control.MenuBar?> +<?import javafx.scene.control.PasswordField?> +<?import javafx.scene.control.Separator?> +<?import javafx.scene.control.TextField?> +<?import javafx.scene.control.TitledPane?> +<?import javafx.scene.image.Image?> +<?import javafx.scene.image.ImageView?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.text.Text?> + +<AnchorPane fx:id="rootWindow" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="605.0" prefWidth="320.0" style="-fx-border-color: black;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <children> + <MenuBar fx:id="menuBar" prefHeight="25.0" prefWidth="320.0" style="-fx-border-color: black;" /> + <ImageView fx:id="closeButton" fitHeight="25.0" fitWidth="25.0" layoutX="295.0" pickOnBounds="true" preserveRatio="true"> + <image> + <Image url="@close.png" /> + </image> + </ImageView> + <Text fx:id="saveSettingsText" layoutX="125.0" layoutY="46.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Save settings" /> + <Text fx:id="nameOfSettingText" layoutX="77.0" layoutY="86.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Name of the new configuration" /> + <TextField fx:id="settingsNameEntry" layoutX="27.0" layoutY="101.0" prefHeight="25.0" prefWidth="264.0" /> + <Accordion fx:id="rootAccordion" layoutX="10.0" layoutY="150.0" prefHeight="280.0" prefWidth="300.0"> + <panes> + <TitledPane fx:id="textEnDecryptRoot" animated="false" text="Text en - / decrypt"> + <content> + <AnchorPane fx:id="textEnDecryptPane" minHeight="0.0" minWidth="0.0" prefWidth="200.0"> + <children> + <Text fx:id="textKeyText" layoutX="10.0" layoutY="40.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Key" /> + <TextField fx:id="textKeyEntry" layoutX="85.0" layoutY="23.0" prefHeight="25.0" prefWidth="175.0" /> + <Text fx:id="textSaltText" layoutX="10.0" layoutY="90.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Salt" /> + <TextField fx:id="textSaltEntry" layoutX="85.0" layoutY="73.0" prefHeight="25.0" prefWidth="175.0" /> + <Text fx:id="textAlgorithmText" layoutX="10.0" layoutY="140.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Algorithm" /> + <ComboBox fx:id="textAlgorithmComboBox" layoutX="85.0" layoutY="123.0" prefHeight="25.0" prefWidth="175.0" /> + </children> + </AnchorPane> + </content> + </TitledPane> + <TitledPane fx:id="fileEnDecryptRoot" animated="false" text="File en- / decrypt"> + <content> + <AnchorPane fx:id="fileEnDecryptPane" minHeight="0.0" minWidth="0.0" prefWidth="200.0"> + <children> + <Text fx:id="fileEnDecryptKeyText" layoutX="10.0" layoutY="40.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Key" /> + <TextField fx:id="fileEnDecryptKeyEntry" layoutX="85.0" layoutY="23.0" prefHeight="25.0" prefWidth="175.0" /> + <Text fx:id="fileEnDecryptSaltText" layoutX="10.0" layoutY="90.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Salt" /> + <TextField fx:id="fileEnDecryptSaltEntry" layoutX="85.0" layoutY="73.0" prefHeight="25.0" prefWidth="175.0" /> + <Text fx:id="fileEnDecryptAlgorithmText" layoutX="10.0" layoutY="140.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Algorithm" /> + <ComboBox fx:id="fileEnDecryptAlgorithmComboBox" layoutX="85.0" layoutY="123.0" prefHeight="25.0" prefWidth="175.0" /> + </children> + </AnchorPane> + </content> + </TitledPane> + <TitledPane fx:id="fileDeleteRoot" animated="false" text="Secure delete files"> + <content> + <AnchorPane fx:id="fileDeletePane" minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0"> + <children> + <Text fx:id="fileDeleteIterationsText" layoutX="14.0" layoutY="94.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Iterations" /> + <TextField fx:id="fileDeleteIterationsEntry" layoutX="85.0" layoutY="77.0" prefHeight="25.0" prefWidth="175.0" /> + </children> + </AnchorPane> + </content> + </TitledPane> + <TitledPane fx:id="settingsRoot" animated="false" text="Settings"> + <content> + <AnchorPane fx:id="settingsPane" minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0"> + <children> + <Text fx:id="fileOutputPathText" layoutX="10.0" layoutY="27.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Default file output path" /> + <TextField fx:id="fileOutputPathEntry" layoutX="10.0" layoutY="41.0" prefHeight="25.0" prefWidth="280.0" /> + <Button fx:id="fileOutputPathButton" layoutX="85.0" layoutY="78.0" mnemonicParsing="false" text="Change output path" /> + <CheckBox fx:id="removeFromFileBoxCheckBox" layoutX="10.0" layoutY="121.0" mnemonicParsing="false" text="Remove files from filebox after en- / decryption" /> + <CheckBox fx:id="limitNumberOfThreadsCheckBox" layoutX="10.0" layoutY="151.0" mnemonicParsing="false" text="Limit number of threads" /> + </children> + </AnchorPane> + </content> + </TitledPane> + </panes> + </Accordion> + <CheckBox fx:id="encryptSettings" layoutX="10.0" layoutY="447.0" mnemonicParsing="false" text="Encrypt settings" /> + <Separator fx:id="separator1" layoutX="10.0" layoutY="474.0" prefWidth="300.0" /> + <PasswordField fx:id="hiddenPasswordEntry" disable="true" layoutX="10.0" layoutY="490.0" prefHeight="25.0" prefWidth="300.0" promptText="Password" /> + <TextField fx:id="showedPasswordEntry" disable="true" layoutX="10.0" layoutY="490.0" prefHeight="25.0" prefWidth="300.0" promptText="Password" visible="false" /> + <CheckBox fx:id="showPassword" disable="true" layoutX="10.0" layoutY="525.0" mnemonicParsing="false" text="Show password" /> + <Separator fx:id="separator2" layoutX="10.0" layoutY="551.0" prefWidth="300.0" /> + <Button fx:id="saveButton" layoutX="131.0" layoutY="564.0" mnemonicParsing="false" prefHeight="25.0" prefWidth="56.0" text="Save" /> + </children> +</AnchorPane> diff --git a/src/org/blueshard/cryptogx/resources/close.png b/src/org/blueshard/cryptogx/resources/close.png new file mode 100644 index 0000000000000000000000000000000000000000..1113eafd348d9b058f08e335d86fcea4d48e3946 GIT binary patch literal 14702 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 literal 0 HcmV?d00001 diff --git a/src/org/blueshard/cryptogx/resources/cryptoGX.png b/src/org/blueshard/cryptogx/resources/cryptoGX.png new file mode 100644 index 0000000000000000000000000000000000000000..f84ad554dda852f1223c06333f7a9226071e505b GIT binary patch literal 51400 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%0AN=LaQB=3_H? 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 literal 0 HcmV?d00001 diff --git a/src/org/blueshard/cryptogx/resources/exportSettingsGUI.fxml b/src/org/blueshard/cryptogx/resources/exportSettingsGUI.fxml new file mode 100644 index 0000000..f40bf75 --- /dev/null +++ b/src/org/blueshard/cryptogx/resources/exportSettingsGUI.fxml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.MenuBar?> +<?import javafx.scene.control.ScrollPane?> +<?import javafx.scene.image.Image?> +<?import javafx.scene.image.ImageView?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Text?> + +<AnchorPane fx:id="mainWindow" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="253.0" prefWidth="254.0" style="-fx-border-color: black;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <children> + <MenuBar fx:id="menuBar" prefHeight="25.0" prefWidth="254.0" style="-fx-border-color: black;" /> + <ImageView fx:id="closeButton" fitHeight="25.0" fitWidth="25.0" layoutX="228.0" layoutY="1.0" pickOnBounds="true" preserveRatio="true"> + <image> + <Image url="@close.png" /> + </image> + </ImageView> + <Text fx:id="exportSettingsText" layoutX="88.0" layoutY="48.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Export settings" /> + <ScrollPane layoutX="7.0" layoutY="64.0" prefHeight="120.0" prefWidth="240.0"> + <content> + <VBox fx:id="settingsBox" prefHeight="118.0" prefWidth="238.0" /> + </content> + </ScrollPane> + <Button fx:id="exportButton" layoutX="98.0" layoutY="203.0" mnemonicParsing="false" text="Export..." /> + </children> +</AnchorPane> diff --git a/src/org/blueshard/cryptogx/resources/loadSettingsGUI.fxml b/src/org/blueshard/cryptogx/resources/loadSettingsGUI.fxml new file mode 100644 index 0000000..df7f741 --- /dev/null +++ b/src/org/blueshard/cryptogx/resources/loadSettingsGUI.fxml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.*?> +<?import javafx.scene.image.*?> +<?import javafx.scene.layout.*?> +<?import javafx.scene.text.*?> + +<AnchorPane fx:id="rootWindow" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="235.0" prefWidth="242.0" style="-fx-border-color: black;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <children> + <MenuBar fx:id="menuBar" prefHeight="25.0" prefWidth="242.0" style="-fx-border-color: black;" /> + <ImageView fx:id="closeButton" fitHeight="25.0" fitWidth="25.0" layoutX="217.0" pickOnBounds="true" preserveRatio="true"> + <image> + <Image url="@close.png" /> + </image> + </ImageView> + <Text fx:id="loadSettingsText" layoutX="86.0" layoutY="50.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Load settings" /> + <ComboBox fx:id="settingsBox" layoutX="24.0" layoutY="72.0" prefHeight="25.0" prefWidth="194.0" /> + <Text fx:id="passwordText" layoutX="14.0" layoutY="136.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Password" /> + <PasswordField fx:id="passwordEntryHide" layoutX="79.0" layoutY="119.0" /> + <TextField fx:id="passwordEntryShow" layoutX="79.0" layoutY="119.0" visible="false" /> + <CheckBox fx:id="showPassword" layoutX="14.0" layoutY="154.0" mnemonicParsing="false" text="Show password" /> + <Separator fx:id="separator1" layoutX="16.0" layoutY="181.0" prefHeight="0.0" prefWidth="211.0" /> + <Button fx:id="loadButton" layoutX="29.0" layoutY="193.0" mnemonicParsing="false" text="Load" /> + <Button fx:id="deleteButton" layoutX="162.0" layoutY="193.0" mnemonicParsing="false" text="Delete" /> + </children> +</AnchorPane> diff --git a/src/org/blueshard/cryptogx/resources/loading.gif b/src/org/blueshard/cryptogx/resources/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..a3ba16a5c19b84c0d0721b350fa003543da29cc7 GIT binary patch literal 9270 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 CwGGk$ literal 0 HcmV?d00001 diff --git a/src/org/blueshard/cryptogx/resources/mainGUI.fxml b/src/org/blueshard/cryptogx/resources/mainGUI.fxml new file mode 100644 index 0000000..39dbbe8 --- /dev/null +++ b/src/org/blueshard/cryptogx/resources/mainGUI.fxml @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.ComboBox?> +<?import javafx.scene.control.Menu?> +<?import javafx.scene.control.MenuBar?> +<?import javafx.scene.control.MenuItem?> +<?import javafx.scene.control.RadioMenuItem?> +<?import javafx.scene.control.ScrollPane?> +<?import javafx.scene.control.Separator?> +<?import javafx.scene.control.SeparatorMenuItem?> +<?import javafx.scene.control.TextArea?> +<?import javafx.scene.control.TextField?> +<?import javafx.scene.image.Image?> +<?import javafx.scene.image.ImageView?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Text?> + +<AnchorPane fx:id="rootWindow" prefHeight="470.0" prefWidth="900.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.blueshard.cryptogx.Controller"> + <children> + <MenuBar fx:id="menubar" prefHeight="25.0" prefWidth="900.0"> + <menus> + <Menu fx:id="fileMenu" mnemonicParsing="false" text="File"> + <items> + <MenuItem fx:id="fileMenuClose" mnemonicParsing="false" onAction="#closeApplication" text="Exit" /> + </items> + </Menu> + <Menu fx:id="settingsMenu" mnemonicParsing="false" text="Settings"> + <items> + <MenuItem fx:id="setDefaultOutputPath" mnemonicParsing="false" text="Set default file en- / decryption output path..." /> + <RadioMenuItem fx:id="removeFileFromFileBox" mnemonicParsing="false" text="Remove files from filebox after en- / decryption" /> + <RadioMenuItem fx:id="limitNumberOfThreads" mnemonicParsing="false" selected="true" text="Limit number of threads" /> + <SeparatorMenuItem fx:id="settingsSeparator1" mnemonicParsing="false" /> + <MenuItem fx:id="saveSettings" mnemonicParsing="false" text="Save settings..." /> + <MenuItem fx:id="loadSettings" disable="true" mnemonicParsing="false" text="Load settings..." /> + <MenuItem fx:id="exportSettings" disable="true" mnemonicParsing="false" text="Export settings..." /> + <MenuItem fx:id="importSettings" mnemonicParsing="false" text="Import settings..." /> + </items></Menu> + <Menu fx:id="helpMenu" mnemonicParsing="false" text="Help" /> + </menus> + </MenuBar> + <ImageView fx:id="minimizeWindow" fitHeight="25.0" fitWidth="25.0" layoutX="850.0" onMouseClicked="#minimizeApplication" pickOnBounds="true" preserveRatio="true"> + <image> + <Image url="@minimize.png" /> + </image></ImageView> + <ImageView fx:id="closeWindow" fitHeight="25.0" fitWidth="25.0" layoutX="875.0" onMouseClicked="#closeApplication" pickOnBounds="true" preserveRatio="true"> + <image> + <Image url="@close.png" /> + </image></ImageView> + <Text fx:id="textText" layoutX="103.0" layoutY="50.0" strokeType="OUTSIDE" strokeWidth="0.0" text="En- / decrypt Text" /> + <TextField fx:id="textKeyEntry" layoutX="76.0" layoutY="64.0" onKeyTyped="#keyTypedTooltip" onMouseExited="#mouseExitEntryTooltip" onMouseMoved="#mouseOverEntryTooltip" promptText="Key" /> + <TextArea fx:id="textDecryptedEntry" layoutX="7.0" layoutY="107.0" onKeyTyped="#keyTypedTooltip" prefHeight="105.0" prefWidth="286.0" promptText="Decrypted Text" /> + <TextArea fx:id="textEncryptedEntry" layoutX="7.0" layoutY="222.0" onKeyTyped="#keyTypedTooltip" prefHeight="105.0" prefWidth="286.0" promptText="Encrypted Text" /> + <Button fx:id="textEncryptButton" layoutX="47.0" layoutY="339.0" mnemonicParsing="false" onAction="#textEncryptButton" text="Encrypt" /> + <ImageView fx:id="textLoadingImage" fitHeight="40.0" fitWidth="40.0" layoutX="131.0" layoutY="332.0" pickOnBounds="true" preserveRatio="true" /> + <Button fx:id="textDecryptButton" layoutX="199.0" layoutY="340.0" mnemonicParsing="false" onAction="#textDecryptButton" text="Decrypt" /> + <Separator fx:id="textSeparator1" layoutX="7.0" layoutY="379.0" prefHeight="8.0" prefWidth="109.0" /> + <Text fx:id="textAdvanced" layoutX="124.0" layoutY="386.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Advanced" wrappingWidth="53.701171875" /> + <Separator fx:id="textSeparator2" layoutX="187.0" layoutY="379.0" prefHeight="8.0" prefWidth="109.0" /> + <Text fx:id="textAlgorithm" layoutX="194.0" layoutY="409.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Algorithm" /> + <TextField fx:id="textSaltEntry" layoutX="14.0" layoutY="419.0" onKeyTyped="#keyTypedTooltip" onMouseExited="#mouseExitEntryTooltip" onMouseMoved="#mouseOverEntryTooltip" prefHeight="25.0" prefWidth="136.0" promptText="Salt" /> + <ComboBox fx:id="textAlgorithmBox" layoutX="156.0" layoutY="419.0" prefHeight="25.0" prefWidth="136.0" /> + <Separator fx:id="midSeparator1" layoutX="300.0" layoutY="35.0" orientation="VERTICAL" prefHeight="424.0" prefWidth="0.0" /> + <Text fx:id="fileEnDecryptText" layoutX="403.0" layoutY="50.0" strokeType="OUTSIDE" strokeWidth="0.0" text="En- / decrypt Files" /> + <TextField fx:id="fileEnDecryptKeyEntry" layoutX="376.0" layoutY="64.0" onKeyTyped="#keyTypedTooltip" onMouseExited="#mouseExitEntryTooltip" onMouseMoved="#mouseOverEntryTooltip" promptText="Key" /> + <Button fx:id="fileEnDecryptFilesButton" layoutX="408.0" layoutY="97.0" mnemonicParsing="false" onAction="#fileEnDecryptChoose" text="Choose files..." /> + <ScrollPane fx:id="fileEnDecryptInputScroll" hbarPolicy="NEVER" layoutX="309.0" layoutY="130.0" onKeyPressed="#onFileEnDecryptPaste" prefHeight="107.0" prefWidth="286.0"> + <content> + <VBox fx:id="fileEnDecryptInputFiles" onDragDropped="#onFileEnDecryptDragNDrop" onDragOver="#onFileEnDecryptDragOver" prefHeight="105.0" prefWidth="282.0" /> + </content> + </ScrollPane> + <Text fx:id="fileEncryptOutputFileText" layoutX="310.0" layoutY="261.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Encrypted File" /> + <TextField fx:id="fileEncryptOutputFile" editable="false" layoutX="390.0" layoutY="244.0" onKeyTyped="#keyTypedTooltip" onMouseExited="#mouseExitEntryTooltip" onMouseMoved="#mouseOverEntryTooltip" prefHeight="25.0" prefWidth="201.0" /> + <Text fx:id="fileDecryptOutputFileText" layoutX="310.0" layoutY="290.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Decrypted File" /> + <TextField fx:id="fileDecryptOutputFile" editable="false" layoutX="390.0" layoutY="273.0" onKeyTyped="#keyTypedTooltip" onMouseExited="#mouseExitEntryTooltip" onMouseMoved="#mouseOverEntryTooltip" prefHeight="25.0" prefWidth="201.0" /> + <Button fx:id="fileEncrypt" layoutX="347.0" layoutY="313.0" mnemonicParsing="false" onAction="#fileEncryptButton" text="Encrypt" /> + <ImageView fx:id="fileEnDecryptLoadingImage" fitHeight="40.0" fitWidth="40.0" layoutX="432.0" layoutY="306.0" pickOnBounds="true" preserveRatio="true" /> + <Button fx:id="fileDecrypt" layoutX="499.0" layoutY="313.0" mnemonicParsing="false" onAction="#fileDecryptButton" text="Decrypt" /> + <Button fx:id="fileEnDecryptStop" layoutX="426.0" layoutY="346.0" mnemonicParsing="false" onAction="#fileEnDecryptCancelButton" text="Cancel" /> + <Separator fx:id="fileEnDecryptSeparator1" layoutX="309.0" layoutY="379.0" prefHeight="8.0" prefWidth="109.0" /> + <Text fx:id="fileEnDecryptAdvanced" layoutX="426.0" layoutY="386.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Advanced" wrappingWidth="53.701171875" /> + <Separator fx:id="fileEnDecryptSeparator2" layoutX="485.0" layoutY="379.0" prefHeight="8.0" prefWidth="109.0" /> + <Text fx:id="fileEnDecryptAlgorithm" layoutX="494.0" layoutY="409.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Algorithm" /> + <TextField fx:id="fileEnDecryptSaltEntry" layoutX="311.0" layoutY="419.0" onKeyTyped="#keyTypedTooltip" onMouseExited="#mouseExitEntryTooltip" onMouseMoved="#mouseOverEntryTooltip" prefHeight="25.0" prefWidth="136.0" promptText="Salt" /> + <ComboBox fx:id="fileEnDecryptAlgorithmBox" layoutX="455.0" layoutY="419.0" prefHeight="25.0" prefWidth="136.0" /> + <Separator fx:id="midSeparator2" layoutX="600.0" layoutY="35.0" orientation="VERTICAL" prefHeight="424.0" prefWidth="0.0" /> + <Text fx:id="fileDeleteText" layoutX="703.0" layoutY="50.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Secure delete files" /> + <Button fx:id="fileEnDecryptFilesButton1" layoutX="708.0" layoutY="64.0" mnemonicParsing="false" onAction="#fileDeleteChoose" text="Choose files..." /> + <ScrollPane fx:id="fileDeleteInputScroll" hbarPolicy="NEVER" layoutX="608.0" layoutY="96.0" prefHeight="228.0" prefWidth="285.0"> + <content> + <VBox fx:id="fileDeleteInputFiles" onDragDropped="#onFileDeleteDragNDrop" onDragOver="#onFileDeleteDragOver" prefHeight="226.0" prefWidth="282.0" /> + </content> + </ScrollPane> + <Button fx:id="fileDeleteButton" layoutX="647.0" layoutY="339.0" mnemonicParsing="false" onAction="#fileDelete" text="Delete" /> + <ImageView fx:id="fileDeleteLoadingImage" fitHeight="40.0" fitWidth="40.0" layoutX="729.0" layoutY="332.0" pickOnBounds="true" preserveRatio="true" /> + <Button fx:id="fileDeleteStop" layoutX="799.0" layoutY="339.0" mnemonicParsing="false" onAction="#fileDeleteCancelButton" text="Cancel" /> + <Separator fx:id="fileDeleteSeparator1" layoutX="610.0" layoutY="379.0" prefHeight="8.0" prefWidth="109.0" /> + <Text fx:id="fileDeleteAdvanced" layoutX="725.0" layoutY="386.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Advanced" wrappingWidth="53.015625" /> + <Separator fx:id="fileDeleteSeparator2" layoutX="781.0" layoutY="379.0" prefHeight="8.0" prefWidth="109.0" /> + <TextField fx:id="fileDeleteIterationsEntry" layoutX="684.0" layoutY="419.0" onKeyTyped="#keyTypedTooltip" onMouseExited="#mouseExitEntryTooltip" onMouseMoved="#mouseOverEntryTooltip" prefHeight="25.0" prefWidth="136.0" promptText="Iterations" text="5" /> + </children> +</AnchorPane> diff --git a/src/org/blueshard/cryptogx/resources/minimize.png b/src/org/blueshard/cryptogx/resources/minimize.png new file mode 100644 index 0000000000000000000000000000000000000000..928735e7810c0c12285d774a3c883e39cfad719a GIT binary patch literal 14698 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 literal 0 HcmV?d00001