diff --git a/src/org/bytedream/cryptogx/Controller.java b/src/org/bytedream/cryptogx/Controller.java new file mode 100644 index 0000000..bb9071c --- /dev/null +++ b/src/org/bytedream/cryptogx/Controller.java @@ -0,0 +1,1647 @@ +package org.bytedream.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.nio.file.CopyOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +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.bytedream.cryptogx.Settings.*; +import static org.bytedream.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 final AtomicInteger textThreads = new AtomicInteger(0); + private final AtomicInteger totalThreads = new AtomicInteger(0); + private final int tooltipShow = 15; + private final int DATAFILEURL = 2; + private final int FILEFILEURL = 1; + private final int NONSPECIFICFILEURL = 0; + private final byte[] buffer = new byte[64]; + 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 currentConfigSettings = new HashMap<>(); + + private final HashMap> enDecryptInputOutputFiles = new HashMap<>(); + private final HashMap> enDecryptInputOutputInternetFiles = new HashMap<>(); + private final HashMap enDecryptInputOutputClipboardImages = new HashMap<>(); + private final HashMap deleteInputFiles = new HashMap<>(); + private final List fileEnDecryptThreads = Collections.synchronizedList(new ArrayList<>()); + private final List fileDeleteThreads = Collections.synchronizedList(new ArrayList<>()); + + private final ContextMenu fileEnDecryptInputContextMenu = new ContextMenu(); + private final ContextMenu fileDeleteInputContextMenu = new ContextMenu(); + private Label choosedLabel = null; + private String choosedLabelType = null; + private final MenuItem fileOutputFileChangeDest = new MenuItem("Change output file"); + private final MenuItem getChoosedLabelInputFileFolder = new MenuItem("Open source directory"); + private final MenuItem getChoosedLabelOutputFileFolder = new MenuItem("Open source directory"); + private final Tooltip tooltip = new Tooltip(); + + public AnchorPane rootWindow; + + public Button fileEnDecryptFilesButton; + public Button fileDecrypt; + public Button fileEncrypt; + public Button fileEnDecryptStop; + + public ComboBox textAlgorithmBox; + public ComboBox fileEnDecryptAlgorithmBox; + + public ImageView minimizeWindow; + public ImageView closeWindow; + public ImageView textLoadingImage; + public ImageView fileEnDecryptLoadingImage; + public ImageView fileDeleteLoadingImage; + + public Menu settingsMenu; + public Menu helpMenu; + + 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-----// + + /** + *

Shows a tooltip when the user type in some text in a text field, text area, etc. and the mouse is over this entry

+ * + * @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; + } + } + } + + /** + *

Shows a tooltip when to mouse is over a text field, text area, etc.

+ * + * @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); + } + } + + /** + *

Hides the tooltip if the mouse exit a text field, text area, etc.

+ */ + public void mouseExitEntryTooltip() { + tooltip.hide(); + } + + //-----menu / close bar-----// + + /** + *

Closed the application. + * Get called if red close button is pressed

+ * + * @since 1.0.0 + */ + public void closeApplication() { + Stage rootStage = (Stage) rootWindow.getScene().getWindow(); + rootStage.close(); + System.exit(0); + } + + /** + *

Hides the application. + * Get called if the green minimize button is pressed

+ * + * @since 1.0.0 + */ + public void minimizeApplication() { + Stage rootStage = (Stage) rootWindow.getScene().getWindow(); + rootStage.setIconified(true); + } + + //-----text-----// + + /** + *

Encrypt text in {@link Controller#textDecryptedEntry}. + * Get called if the text 'Encrypt' button is pressed

+ * + * @since 1.0.0 + */ + 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(); + String textAlgorithm = textAlgorithmBox.getSelectionModel().getSelectedItem(); + EnDecrypt.AES encrypt = new EnDecrypt.AES(textKeyEntry.getText(), salt, Integer.parseInt(textAlgorithm.substring(textAlgorithm.indexOf('-') + 1))); + 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; + } + + /** + *

Decrypt text in {@link Controller#textEncryptedEntry}. + * Get called if the text 'Decrypt' button is pressed

+ * + * @since 1.0.0 + */ + 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(); + String textAlgorithm = textAlgorithmBox.getSelectionModel().getSelectedItem(); + EnDecrypt.AES decrypt = new EnDecrypt.AES(textKeyEntry.getText(), salt, Integer.parseInt(textAlgorithm.substring(textAlgorithm.indexOf('-') + 1))); + 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-----// + + /** + *

Synchronized method to get the list of threads which en- / decrypt files

+ * + * @return list of en- / decryption threads + */ + private synchronized List getFileEnDecryptThreads() { + return fileEnDecryptThreads; + } + + /** + *

Synchronized method to get the number of threads which en- / decrypt files

+ * + * @return number of en- / decryption threads + * + * @since 1.2.0 + */ + private synchronized int getFileEnDecryptThreadsSize() { + return fileEnDecryptThreads.size(); + } + + /** + *

Synchronized method to add a thread to the file en- / decryption list of current running file en- / decryption threads

+ * + * @param thread that should be added + * + * @since 1.2.0 + */ + private synchronized void addFileEnDecryptThread(Thread thread) { + fileEnDecryptThreads.add(thread); + } + + /** + *

Synchronized method to remove a thread from the file en- / decryption list of current running file en- / decryption threads

+ * + * @param thread that should be removed + * + * @since 1.2.0 + */ + private synchronized void removeFileEnDecryptThread(Thread thread) { + fileEnDecryptThreads.remove(thread); + } + + /** + *

Adds a file for en- / decryption

+ * + * @param file that should be added + * + * @since 1.0.0 + */ + 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; + String fileOutputPath = file.getParent() + "/";; + String fileEnding; + ArrayList inputOutputList = new ArrayList<>(); + if (!currentConfigSettings.get("fileOutputPath").trim().isEmpty()) { + fileOutputPath = currentConfigSettings.get("fileOutputPath").trim() + "/"; + } + if (file.isFile()) { + fileEnding = ".cryptoGX"; + } else { + fileEnding = "_cryptoGX"; + } + encryptFile = new File(fileOutputPath + fileName + fileEnding); + while (encryptFile.exists()) { + encryptFile = new File(encryptFile.getAbsolutePath() + fileEnding); + } + if (fileAbsolutePath.endsWith(".cryptoGX") || fileAbsolutePath.endsWith("_cryptoGX")) { + decryptFile = new File(fileOutputPath + fileName.substring(0, fileName.length() - 9)); + } else { + decryptFile = new File(fileOutputPath + fileName + fileEnding); + } + while (decryptFile.exists()) { + decryptFile = new File(decryptFile.getAbsolutePath() + fileEnding); + } + inputOutputList.add(0, encryptFile); + inputOutputList.add(1, decryptFile); + fileEnDecryptInputFiles.getChildren().add(newLabel); + enDecryptInputOutputFiles.put(newLabel, inputOutputList); + } + + /** + *

Adds an file from the internet for en- / decryption

+ * + * @param url of the file + * @param fileType of the file + * @throws URISyntaxException + * + * @since 1.5.0 + */ + private void fileEnDecryptAddInternetFile(String url, int fileType) throws URISyntaxException { + String filename; + switch (fileType) { + case FILEFILEURL: + filename = url.substring(url.lastIndexOf("/") + 1); + break; + case DATAFILEURL: + filename = url.substring(5, url.indexOf("/")) + "." + url.substring(url.indexOf("/") + 1, url.indexOf(";")); + break; + case NONSPECIFICFILEURL: + filename = "unknown" + System.nanoTime(); + break; + default: + 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 fileSpecs = new ArrayList<>(); + ArrayList 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); + } + + /** + *

Adds an clipboard image for en- / decryption. + * This can be a normal image and an image stream

+ * + * @param image that should be added + * @throws URISyntaxException + * + * @since 1.7.0 + */ + 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 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); + } + + /** + *

Changes the text in the file en- / decryption output file text fields

+ * + * @param label + * @param encryptOutputFile is the filename of the file it gets encrypted + * @param decryptOutputFile is the filename of the file it gets decrypted + * + * @since 1.2.0 + */ + private void fileOutputFilesChangeText(Label label, String encryptOutputFile, String decryptOutputFile) { + File encryptFile; + File decryptFile; + ArrayList 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); + } + + /** + *

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

+ * + * @param label that should be deleted + * + * @since 1.2.0 + */ + 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; + } + } + } + + /** + *

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

+ * + * @param changeLabel is the label that the user has clicked + * + * @since 1.0.0 + */ + 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); + } + } + + /** + *

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

+ * + * @since 1.12.0 + */ + public void fileEnDecryptChooseFiles() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Choose files"); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("All files", "*.*")); + List files = fileChooser.showOpenMultipleDialog(rootWindow.getScene().getWindow()); + try { + if (files.size() > 0) { + files.forEach(this::fileEnDecryptAddFile); + } + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + + /** + *

Opens a directory chooser GUI where the user can select the directories that should be en- / decrypted. + * Get called if the 'directories...' in the file en- / decrypt section button is pressed

+ * + * @since 1.12.0 + */ + public void fileEnDecryptChooseDirectories() { + DirectoryChooser directoryChooser = new DirectoryChooser(); + directoryChooser.setTitle("Choose directories"); + File file = directoryChooser.showDialog(rootWindow.getScene().getWindow()); + try { + fileEnDecryptAddFile(file); + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + + /** + *

Get called if user drags a (normal or internet) file over the en- / decrypt file box

+ * + * @param event source + * + * @since 1.2.0 + */ + public void onFileEnDecryptDragOver(DragEvent event) { + Dragboard dragboard = event.getDragboard(); + if (event.getGestureSource() != fileEnDecryptInputFiles) { + if (dragboard.hasFiles()) { + 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); + } + } + } + } + + /** + *

Get called if the user drops the dragged (normal or internet) file over the en- / decrypt file box

+ * + * @param event source + * @throws URISyntaxException + * + * @since 1.2.0 + */ + public void onFileEnDecryptDragNDrop(DragEvent event) throws URISyntaxException { + Dragboard dragboard = event.getDragboard(); + if (dragboard.hasFiles()) { + dragboard.getFiles().forEach(this::fileEnDecryptAddFile); + } 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); + } + } + } + + /** + *

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

+ * + * @param event source + * @throws URISyntaxException + * + * @since 1.7.0 + */ + 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(); + } + } + } + + /** + *

Encrypt all files given files. + * Get called if file 'Encrypt' button is pressed

+ * + * @since 1.0.0 + */ + 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> 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 outputFileList = entry.getValue(); + String fileEnDecryptAlgorithm = fileEnDecryptAlgorithmBox.getSelectionModel().getSelectedItem(); + EnDecrypt.AES fileEncrypt = new EnDecrypt.AES(fileEnDecryptKeyEntry.getText(), salt, Integer.parseInt(fileEnDecryptAlgorithm.substring(fileEnDecryptAlgorithm.indexOf('-') + 1))); + if (enDecryptInputOutputInternetFiles.containsKey(inputFileLabel)) { + ArrayList fileSpecs = enDecryptInputOutputInternetFiles.get(inputFileLabel); + int urlType = (int) fileSpecs.get(0); + String url = (String) fileSpecs.get(1); + try { + if (urlType == FILEFILEURL || urlType == NONSPECIFICFILEURL) { + URLConnection openURL = new URL(url).openConnection(); + openURL.addRequestProperty("User-Agent", "Mozilla/5.0"); + fileEncrypt.encryptFile(openURL.getInputStream(), new FileOutputStream((File) fileSpecs.get(2)), buffer); + } 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.encryptFile(new ByteArrayInputStream(decoded), new FileOutputStream((File) fileSpecs.get(2)), buffer); + } + } catch (FileNotFoundException | InvalidKeySpecException | NoSuchAlgorithmException | MalformedURLException | InvalidKeyException | NoSuchPaddingException 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.encryptFile(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()), new FileOutputStream(outputFileList.get(0).getAbsoluteFile()), buffer); + } catch (IOException e) { + e.printStackTrace(); + Platform.runLater(() -> errorAlert("IO Exception occurred", e.getMessage())); + } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | InvalidKeySpecException e) { + e.printStackTrace(); + } + } else { + try { + File inputFile = new File(inputFileLabel.getText()); + if (inputFile.isFile()) { + fileEncrypt.encryptFile(new FileInputStream(inputFile), new FileOutputStream(outputFileList.get(0)), buffer); + } else { + fileEncrypt.encryptDirectory(inputFileLabel.getText(), outputFileList.get(0).getAbsolutePath(), ".cryptoGX", buffer); + if (!outputFileList.get(0).isDirectory()) { + Platform.runLater(() -> warningAlert("Couldn't create directory\n '" + outputFileList.get(0).getAbsolutePath() + "'.\nTry again or restart cryptoGX with admin privileges")); + } + } + } catch (NoSuchPaddingException | 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; + } + } + } + + /** + *

Decrypt all files given files. + * Get called if file 'Decrypt' button is pressed

+ * + * @since 1.0.0 + */ + 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> 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 outputFileList = entry.getValue(); + String fileEnDecryptAlgorithm = fileEnDecryptAlgorithmBox.getSelectionModel().getSelectedItem(); + EnDecrypt.AES fileDecrypt = new EnDecrypt.AES(fileEnDecryptKeyEntry.getText(), salt, Integer.parseInt(fileEnDecryptAlgorithm.substring(fileEnDecryptAlgorithm.indexOf('-') + 1))); + if (enDecryptInputOutputInternetFiles.containsKey(entry.getKey())) { + ArrayList 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.decryptFile(openURL.getInputStream(), new FileOutputStream((File) imageSpecs.get(2)), buffer); + fileDecrypt.decryptFile(openURL.getInputStream(), new FileOutputStream((File) imageSpecs.get(2)), buffer); + } 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.decryptFile(new ByteArrayInputStream(decoded), new FileOutputStream((File) imageSpecs.get(2)), buffer); + } else if (urlType == NONSPECIFICFILEURL) { + URLConnection openURL = new URL(url).openConnection(); + openURL.addRequestProperty("User-Agent", "Mozilla/5.0"); + fileDecrypt.decryptFile(openURL.getInputStream(), new FileOutputStream((File) imageSpecs.get(2)), buffer); + } + } catch (FileNotFoundException | InvalidKeySpecException | NoSuchAlgorithmException | MalformedURLException | InvalidKeyException | NoSuchPaddingException 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.decryptFile(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()), new FileOutputStream(outputFileList.get(1).getAbsolutePath()), buffer); + } catch (IOException e) { + e.printStackTrace(); + Platform.runLater(() -> errorAlert("IO Exception occurred", e.getMessage())); + } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | InvalidKeySpecException e) { + e.printStackTrace(); + } + } else { + try { + File inputFile = new File(inputFileLabel.getText()); + if (inputFile.isFile()) { + fileDecrypt.decryptFile(new FileInputStream(inputFile), new FileOutputStream(outputFileList.get(1)), buffer); + } else { + fileDecrypt.decryptDirectory(inputFileLabel.getText(), outputFileList.get(1).getAbsolutePath(), "@.cryptoGX@", buffer); + if (!outputFileList.get(1).isDirectory()) { + Platform.runLater(() -> warningAlert("Couldn't create directory\n '" + outputFileList.get(1).getAbsolutePath() + "'.\nTry again or restart cryptoGX with admin privileges")); + } + } + } catch (NoSuchPaddingException | InvalidKeySpecException | InvalidKeyException | NoSuchAlgorithmException 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; + } + } + } + + /** + *

Cancels the file en- / decryption. + * Get called if the file en- / decrypt 'Cancel' button is pressed

+ * + * @since 1.12.0 + */ + public void fileEnDecryptCancelButton() { + for (Iterator 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-----// + + /** + *

Synchronized method to get the list of threads which delete files

+ * + * @return list of threads which delete files + * + * @since 1.2.0 + */ + private synchronized List getFileDeleteThreads() { + return fileDeleteThreads; + } + + /** + *

Synchronized method to get the number of threads which delete files

+ * + * @return number of threads which delete files + * + * @since 1.2.0 + */ + private synchronized int getFileDeleteThreadsSize() { + return fileDeleteThreads.size(); + } + + /** + *

Synchronized method to add a thread to the file delete list of current running file delete threads

+ * + * @param thread that should be added + * + * @since 1.2.0 + */ + private synchronized void addFileDeleteThread(Thread thread) { + fileDeleteThreads.add(thread); + } + + /** + *

Synchronized method to remove a thread from the file delete list of current file delete threads

+ * + * @param thread that should be removed + * + * @since 1.2.0 + */ + private synchronized void removeFileDeleteThread(Thread thread) { + fileDeleteThreads.remove(thread); + } + + /** + *

Adds a file that should be deleted

+ * + * @param file that should be added + * + * @since 1.2.0 + */ + 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)); + newLabel.setContextMenu(fileDeleteInputContextMenu); + fileDeleteInputFiles.getChildren().add(newLabel); + deleteInputFiles.put(newLabel, file.getAbsoluteFile()); + } + + /** + *

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

+ * + * @param changeLabel is the label that the user has clicked + * + * @since 1.2.0 + */ + 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)); + } + } + + /** + *

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

+ * + * @param label that should be deleted + * + * @since 1.12.0 + */ + private void fileDeleteDeleteEntry(Label label) { + deleteInputFiles.remove(choosedLabel); + if (fileDeleteInputFiles.getChildren().size() - 1 >= 1) { + for (int i=0; iOpens 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

+ * + * @since 1.12.0 + */ + public void fileDeleteChooseFiles() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Choose files"); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("All Files", "*.*")); + List files = fileChooser.showOpenMultipleDialog(rootWindow.getScene().getWindow()); + try { + if (files.size() > 0) { + files.forEach(this::fileDeleteAddFile); + } + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + + /** + *

Opens a directory chooser GUI where the user can select the directories that should be en- / decrypted. + * Get called if the 'Choose directories...' in the delete section button is pressed

+ * + * @since 1.12.0 + */ + public void fileDeleteChooseDirectories() { + DirectoryChooser directoryChooser = new DirectoryChooser(); + directoryChooser.setTitle("Choose directories"); + File file = directoryChooser.showDialog(rootWindow.getScene().getWindow()); + try { + fileDeleteAddFile(file); + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + + /** + *

Get called if user drags a file over the delete file box

+ * + * @param event source + * + * @since 1.2.0 + */ + public void onFileDeleteDragOver(DragEvent event) { + Dragboard dragboard = event.getDragboard(); + if (event.getGestureSource() != fileDeleteInputFiles && dragboard.hasFiles()) { + event.acceptTransferModes(TransferMode.COPY_OR_MOVE); + } + } + + /** + *

Get called if the user drops the dragged file over the delete file box

+ * + * @param event source + * + * @since 1.2.0 + */ + public void onFileDeleteDragNDrop(DragEvent event) { + Dragboard dragboard = event.getDragboard(); + if (dragboard.hasFiles()) { + dragboard.getFiles().forEach(file -> { + if (file.isFile() || file.isDirectory()) { + fileDeleteAddFile(file); + } + }); + } + } + + /** + *

Delete all given files. + * Get called if 'Delete' button is pressed

+ * + * @since 1.2.0 + */ + public void fileDelete() { + if (!fileDeleteLoading && !deleteInputFiles.isEmpty()) { + fileDeleteLoadingImage.setImage(loadingImage); + } + int deleteIterations = Integer.parseInt(fileDeleteIterationsEntry.getText()); + for (Map.Entry map : deleteInputFiles.entrySet()) { + 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(); + try { + if (file.isFile()) { + SecureDelete.deleteFile(file, deleteIterations, buffer); + } else if (file.isDirectory()) { + SecureDelete.deleteDirectory(file.getAbsolutePath(), deleteIterations, buffer); + } + } catch (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; + } + } + + /** + *

Cancels the file en- / decryption. + * Get called if the file delete 'Cancel' button is pressed

+ * + * @since 1.12.0 + */ + public void fileDeleteCancelButton() { + for (Iterator 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 + * null if the location is not known. + * + * @param resources + * The resources used to localize the root object, or null if + * the root object was not localized. + * + * @since 1.0.0 + */ + @Override + public void initialize(URL location, ResourceBundle resources) { + + //-----general-----// + + 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)); + + 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(choosedLabel); + } else if (choosedLabelType.equals("DELETE")) { + fileDeleteDeleteEntry(choosedLabel); + } + } + }); + + 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) loadSettingsGUI(rootWindow.getScene().getWindow()).values().toArray()[0]; + 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.deleteFile(config, 5, buffer); + isConfig = false; + } catch (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) { + writeSettings(config, readSettings(file)); + } else { + writeSettings(config, readSettings(file)); + isConfig = true; + } + } + }); + + //-----text------// + + textAlgorithmBox.setItems(FXCollections.observableArrayList(Utils.algorithms.keySet())); + textAlgorithmBox.setValue(Utils.algorithms.keySet().toArray(new String[Utils.algorithms.size()])[0]); + + //-----fileEnDecrypt-----// + + fileEnDecryptAlgorithmBox.setItems(FXCollections.observableArrayList(Utils.algorithms.keySet())); + fileEnDecryptAlgorithmBox.setValue(Utils.algorithms.keySet().toArray(new String[Utils.algorithms.size()])[0]); + + MenuItem enDecryptRemove = new MenuItem(); + enDecryptRemove.setText("Remove"); + enDecryptRemove.setOnAction(removeEvent -> fileEnDecryptDeleteEntry(choosedLabel)); + MenuItem enDecryptChangeDest = new MenuItem(); + enDecryptChangeDest.setText("Change output file / directory"); + enDecryptChangeDest.setOnAction(outputFileChangeEvent -> { + File file; + if (new File(choosedLabel.getText()).isFile()) { + FileChooser destChooser = new FileChooser(); + destChooser.setTitle("Choose or create new file"); + destChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("All files", "*.*")); + file = destChooser.showSaveDialog(rootWindow.getScene().getWindow()); + } else { + DirectoryChooser destChooser = new DirectoryChooser(); + destChooser.setTitle("Choose or create new directory"); + file = destChooser.showDialog(rootWindow.getScene().getWindow()); + } + if (file != null) { + for (Map.Entry> entry : enDecryptInputOutputFiles.entrySet()) { + if (entry.getKey().getText().equals(choosedLabel.getText())) { + ArrayList 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> entry : enDecryptInputOutputFiles.entrySet()) { + if (entry.getKey().getText().equals(choosedLabel.getText())) { + ArrayList 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(choosedLabel)); + fileDeleteInputContextMenu.getItems().add(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) loadSettingsGUI(rootWindow.getScene().getWindow()).values().toArray()[0]; + 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.deleteFile(config, 5, buffer); + isConfig = false; + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + } + }); + t.start(); + } +} diff --git a/src/org/bytedream/cryptogx/EnDecrypt.java b/src/org/bytedream/cryptogx/EnDecrypt.java new file mode 100644 index 0000000..bf7fbd9 --- /dev/null +++ b/src/org/bytedream/cryptogx/EnDecrypt.java @@ -0,0 +1,302 @@ +package org.bytedream.cryptogx; + +import javax.crypto.*; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + *

Class for en- / decrypt text and files

+ * + * @since 1.0.0 + */ +public class EnDecrypt { + + public static class AES extends Thread { + + public int iterations = 65536; + + private final String secretKeyFactoryAlgorithm = "PBKDF2WithHmacSHA1"; + private int keySize = 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 keySize) { + this.key = key; + this.salt = salt; + this.keySize = keySize; + } + + public AES(String key, byte[] salt, int iterations, int keySize) { + this.key = key; + this.salt = salt; + this.iterations = iterations; + this.keySize = keySize; + } + + /** + *

Creates a secret key from given (plain text) key and salt

+ * + * @return the secret key + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + * + * @since 1.0.0 + */ + private byte[] createSecretKey() throws NoSuchAlgorithmException, InvalidKeySpecException { + SecretKeyFactory factory = SecretKeyFactory.getInstance(secretKeyFactoryAlgorithm); + PBEKeySpec keySpec = new PBEKeySpec(key.toCharArray(), salt, iterations, keySize); + + return factory.generateSecret(keySpec).getEncoded(); + } + + /** + *

Writes {@param inputStream} to {@param outputStream}

+ * + * @param inputStream from which is written + * @param outputStream to which is written + * @param buffer + * @throws IOException + * + * @since 1.12.0 + */ + private void write(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(); + } + + /** + *

Encrypts the {@param inputStream} to {@param outputStream}

+ * + * @param inputStream that should be encrypted + * @param outputStream to which the encrypted {@param inputFile} should be written to + * @param buffer + * @throws InvalidKeySpecException + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws IOException + * + * @since 1.12.0 + */ + public void encryptFile(InputStream inputStream, OutputStream outputStream, byte[] buffer) throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException { + Key secretKey = new SecretKeySpec(createSecretKey(), "AES"); + + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + + CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher); + write(cipherInputStream, outputStream, buffer); + } + + /** + *

Encrypts all files in the {@param inputDirectory} to the {@param outputDirectory}

+ * + * @param inputDirectory that should be encrypted + * @param outputDirectory to which the encrypted {@param inputDirectory} files should be written to + * @param fileEnding get added to every file that gets encrypted (if the {@param fileEnding} starts and ends with + * a '@', the {@param fileEnding} will get removed from the file if it exists) + * @param buffer + * @throws InvalidKeySpecException + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws IOException + * + * @since 1.12.0 + */ + public void encryptDirectory(String inputDirectory, String outputDirectory, String fileEnding, byte[] buffer) throws IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException, NoSuchPaddingException { + AtomicBoolean remove = new AtomicBoolean(false); + + if (fileEnding == null) { + fileEnding = ""; + } else if (fileEnding.startsWith("@") && fileEnding.endsWith("@")) { + fileEnding = fileEnding.substring(1, fileEnding.length() - 1); + remove.set(true); + } + + HashMap files = new HashMap<>(); + final String finalFileEnding = fileEnding; + Files.walk(Paths.get(inputDirectory)).map(Path::toFile).forEach(oldFile -> { + String oldFilePath = oldFile.getAbsolutePath(); + if (oldFile.isDirectory()) { + new File(oldFilePath.replace(inputDirectory, outputDirectory + "/")).mkdir(); + }else if (remove.get() && oldFilePath.endsWith(finalFileEnding)) { + files.put(oldFile, new File(oldFilePath.substring(0, oldFilePath.lastIndexOf(finalFileEnding)) + .replace(inputDirectory, outputDirectory + "/") + finalFileEnding)); + } else { + files.put(oldFile, new File(oldFilePath.replace(inputDirectory, outputDirectory + "/") + finalFileEnding)); + } + }); + + File newFile; + for (Map.Entry entry: files.entrySet()) { + newFile = entry.getValue(); + encryptFile(new FileInputStream(entry.getKey()), new FileOutputStream(newFile), buffer); + } + } + + /** + *

Decrypts the {@param inputStream} to {@param outputStream}

+ * + * @param inputStream that should be decrypted + * @param outputStream to which the decrypted {@param inputFile} should be written to + * @param buffer + * @throws InvalidKeySpecException + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws IOException + * @throws InvalidAlgorithmParameterException + * + * @since 1.12.0 + */ + public void decryptFile(InputStream inputStream, OutputStream outputStream, byte[] buffer) throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException{ + Key secretKey = new SecretKeySpec(createSecretKey(), "AES"); + + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + + CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher); + write(inputStream, cipherOutputStream, buffer); + } + + /** + *

Decrypts all files in the {@param inputDirectory} to the {@param outputDirectory}

+ * + * @param inputDirectory that should be decrypted + * @param outputDirectory to which the decrypted {@param inputDirectory} files should be written to + * @param fileEnding get added to every file that gets decrypted (if the {@param fileEnding} starts and ends with + * a '@', the {@param fileEnding} will get removed from the file if it exists) + * @param buffer + * @throws InvalidKeySpecException + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws IOException + * + * @since 1.12.0 + */ + public void decryptDirectory(String inputDirectory, String outputDirectory, String fileEnding, byte[] buffer) throws IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException, NoSuchPaddingException { + AtomicBoolean remove = new AtomicBoolean(false); + + if (fileEnding == null) { + fileEnding = ""; + } else if (fileEnding.startsWith("@") && fileEnding.endsWith("@")) { + fileEnding = fileEnding.substring(1, fileEnding.length() - 1); + remove.set(true); + } + + HashMap files = new HashMap<>(); + final String finalFileEnding = fileEnding; + Files.walk(Paths.get(inputDirectory)).map(Path::toFile).forEach(oldFile -> { + String oldFilePath = oldFile.getAbsolutePath(); + if (oldFile.isDirectory()) { + new File(oldFilePath.replace(inputDirectory, outputDirectory + "/")).mkdir(); + } + else if (remove.get() && oldFilePath.endsWith(finalFileEnding)) { + files.put(oldFile, new File(oldFilePath.substring(0, oldFilePath.lastIndexOf(finalFileEnding)) + .replace(inputDirectory, outputDirectory + "/") + finalFileEnding)); + } else { + files.put(oldFile, new File(oldFilePath.replace(inputDirectory, outputDirectory + "/") + finalFileEnding)); + } + }); + + File newFile; + for (Map.Entry entry: files.entrySet()) { + newFile = entry.getValue(); + decryptFile(new FileInputStream(entry.getKey()), new FileOutputStream(newFile), buffer); + } + } + + /** + *

Encrypt {@param bytes}

+ * + * @param bytes that should be encrypted + * @return encrypted bytes + * @throws BadPaddingException + * @throws IllegalBlockSizeException + * @throws NoSuchPaddingException + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + * @throws InvalidKeyException + * + * @since 1.0.0 + */ + public byte[] encrypt(byte[] bytes) throws BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + Key secretKey = new SecretKeySpec(createSecretKey(), "AES"); + + Cipher encryptCipher = Cipher.getInstance("AES"); + encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey); + return encryptCipher.doFinal(bytes); + } + + /** + *

Encrypt {@param bytes}

+ * + * @param string that should be encrypted + * + * @see EnDecrypt.AES#encrypt(byte[]) + * + * @since 1.0.0 + */ + public String encrypt(String string) throws BadPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, NoSuchPaddingException, InvalidKeyException, InvalidKeySpecException { + return Base64.getEncoder().encodeToString(encrypt(string.getBytes(StandardCharsets.UTF_8))); + } + + /** + *

Decrypt encrypted {@param bytes}

+ * + * @param bytes that should be decrypted + * @return decrypted bytes + * @throws BadPaddingException + * @throws IllegalBlockSizeException + * @throws NoSuchPaddingException + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + * @throws InvalidKeyException + * + * @since 1.12.0 + */ + public byte[] decrypt(byte[] bytes) throws BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + Key secretKey = new SecretKeySpec(createSecretKey(), "AES"); + + Cipher decryptCipher = Cipher.getInstance("AES"); + decryptCipher.init(Cipher.DECRYPT_MODE, secretKey); + return decryptCipher.doFinal(Base64.getDecoder().decode(bytes)); + } + + /** + *

Decrypt encrypted {@param string}

+ * + * @param string that should be decrypted + * + * @see EnDecrypt.AES#decrypt(byte[]) + * + * @since 1.0.0 + */ + public String decrypt(String string) throws BadPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, NoSuchPaddingException, InvalidKeyException, InvalidKeySpecException { + return new String(decrypt(string.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8); + } + + } +} diff --git a/src/org/bytedream/cryptogx/Main.java b/src/org/bytedream/cryptogx/Main.java new file mode 100644 index 0000000..437a4a3 --- /dev/null +++ b/src/org/bytedream/cryptogx/Main.java @@ -0,0 +1,326 @@ +/* + * @author bytedream + * @version 1.12.0 + * + * Some @since versions may be not correct, because the @since tag got added in + * version 1.12.0 and I don't have all versions (1.0.0 - 1.11.0), so I cannot see when some methods were added + + */ + +package org.bytedream.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; + +/** + *

Main class

+ * + * @since 1.0.0 + */ +public class Main extends Application { + + protected static final int NON_PORTABLE = 1; + protected static final int PORTABLE = 0; + + protected static final int TYPE = NON_PORTABLE; + + protected final static String configDefaultTextKey = ""; + protected final static String configDefaultTextSalt = ""; + protected final static String configDefaultTextAlgorithm = "AES-128"; + protected final static String configDefaultFileEnDecryptKey = ""; + protected final static String configDefaultFileEnDecryptSalt = ""; + protected final static String configDefaultFileEnDecryptAlgorithm = "AES-128"; + protected final static int configDefaultFileDeleteIterations = 5; + protected final static String configDefaultFileOutputPath = ""; + protected final static boolean configDefaultRemoveFileFromFileBox = false; + protected final static boolean configDefaultLimitNumberOfThreads = true; + + private final static byte[] buffer = new byte[64]; + + private static Stage mainStage; + private double rootWindowX, rootWindowY; + protected static File config; + protected static boolean isConfig; + + /** + *

Start the GUI

+ * + * @param primaryStage of the GUI + * @throws IOException if issues with loading 'mainGUI.fxml' + * + * @since 1.0.0 + */ + @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); + //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(); + } + + /** + *

Enter method for the application. + * Can also be used to en- / decrypt text and files or secure delete files without starting GUI

+ * + * @param args from the command line + * @return + * @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) + * + * @since 1.0.0 + */ + public static void main(String[] args) throws BadPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, NoSuchPaddingException, InvalidKeyException, InvalidKeySpecException, IOException, InvalidAlgorithmParameterException { + if (Main.TYPE == Main.PORTABLE) { + String system = System.getProperty("os.name").toLowerCase(); + if (system.startsWith("windows")) { + config = new File("C:\\Users\\" + System.getProperty("user.name") + "\\AppData\\Roaming\\cryptoGX\\cryptoGX.config"); + File directory = new File("C:\\Users\\" + System.getProperty("user.name") + "\\AppData\\Roaming\\cryptoGX"); + if (!directory.isDirectory()) { + directory.mkdir(); + } + } else if (system.startsWith("linux")) { + config = new File(System.getProperty("user.home") + "/.cryptoGX/cryptoGX.config"); + File directory = new File(System.getProperty("user.home") + "/.cryptoGX/"); + if (!directory.isDirectory()) { + directory.mkdir(); + } + } else { + config = new File("cryptoGX.config"); + } + } else { + config = new File("cryptoGX.config"); + } + isConfig = config.isFile(); + if (args.length == 0) { + 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: AES encrypt \n" + + " decrypt: AES decrypt \n\n" + + " File en- / decryption\n" + + " encrypt: AES encrypt \n" + + " decrypt: AES decrypt \n\n" + + "File secure delete: delete "); //for the argument 'default' can be used, which is 5 + } 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 { + if (args[1].equals("default")) { + args[1] = "5"; + } + File deleteFile = new File(args[2]); + if (deleteFile.isFile()) { + SecureDelete.deleteFile(deleteFile, Integer.parseInt(args[1]), buffer); + } else if (deleteFile.isDirectory()) { + SecureDelete.deleteDirectory(args[2], Integer.parseInt(args[1]), buffer); + } else { + System.err.println("Couldn't find file " + args[4]); + System.exit(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 < 5) { + System.err.println("To few arguments were given"); + System.exit(1); + } else if (args.length > 6) { + System.err.println("To many 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 (args.length == 5) { + if (type.equals("encrypt")) { + System.out.println(Base64.getEncoder().encodeToString(aes.encrypt(args[4].getBytes(StandardCharsets.UTF_8)))); + } else if (type.equals("decrypt")) { + System.out.println(aes.decrypt(args[4])); + } else { + System.err.println("Couldn't resolve argument " + args[3] + ", expected 'encrypt' or 'decrypt'"); + System.exit(1); + } + } else { + if (type.equals("encrypt")) { + File inputFile = new File(args[4]); + if (inputFile.isFile()) { + aes.encryptFile(new FileInputStream(inputFile), new FileOutputStream(args[5]), new byte[64]); + } else if (inputFile.isDirectory()) { + aes.encryptDirectory(args[4], args[5], ".cryptoGX", new byte[64]); + } else { + System.err.println("Couldn't find file " + args[4]); + System.exit(1); + } + } else if (type.equals("decrypt")) { + File inputFile = new File(args[4]); + if (inputFile.isFile()) { + aes.decryptFile(new FileInputStream(inputFile), new FileOutputStream(args[5]), new byte[64]); + } else if (inputFile.isDirectory()) { + aes.decryptDirectory(args[4], args[5], "@.cryptoGX@", new byte[64]); + } else { + System.err.println("Couldn't find file " + args[4]); + System.exit(1); + } + } else { + System.err.println("Couldn't resolve argument " + args[3] + ", expected 'encrypt' or 'decrypt'"); + System.exit(1); + } + } + System.exit(0); + } + } + } + + /** + *

"Catch" all uncatched exceptions and opens an alert window

+ * + * @param thread which called this method + * @param throwable of the thread which called the method + * + * @since 1.3.0 + */ + private static void exceptionAlert(Thread thread, Throwable throwable) { + throwable.printStackTrace(); + + AtomicReference exceptionAlertX = new AtomicReference<>(Screen.getPrimary().getBounds().getMaxX() / 2); + AtomicReference 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"); + enDecryptError.setResizable(true); + ((Stage) enDecryptError.getDialogPane().getScene().getWindow()).getIcons().add(new Image(Main.class.getResource("resources/cryptoGX.png").toExternalForm())); + enDecryptError.getDialogPane().setContent(new Label("Error: " + throwable)); + + 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(); + } + + /** + *

Shows an error alert window

+ * + * @param message which will the alert show + * @param error which will show after the message + */ + protected static void errorAlert(String message, String error) { + AtomicReference alertX = new AtomicReference<>(Screen.getPrimary().getBounds().getMaxX() / 2); + AtomicReference 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"); + enDecryptError.setResizable(true); + ((Stage) enDecryptError.getDialogPane().getScene().getWindow()).getIcons().add(new Image(Main.class.getResource("resources/cryptoGX.png").toExternalForm())); + enDecryptError.getDialogPane().setContent(new Label(message)); + + 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(); + } + + /** + *

Shows an warning alert window

+ * + * @param message that the alert window will show + * + * @since 1.4.0 + */ + protected static void warningAlert(String message) { + AtomicReference alertX = new AtomicReference<>(Screen.getPrimary().getBounds().getMaxX() / 2); + AtomicReference 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())); + enDecryptError.getDialogPane().setContent(new Label(message)); + + 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/bytedream/cryptogx/SecureDelete.java b/src/org/bytedream/cryptogx/SecureDelete.java new file mode 100644 index 0000000..0d1ee05 --- /dev/null +++ b/src/org/bytedream/cryptogx/SecureDelete.java @@ -0,0 +1,90 @@ +package org.bytedream.cryptogx; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; +import java.util.TreeSet; + +/** + *

Class for secure delete files

+ * + * @since 1.2.0 + */ +public class SecureDelete { + + public static void deleteDirectory(String directory, int iterations, byte[] buffer) throws IOException { + TreeSet directories = new TreeSet<>(); + Files.walk(Paths.get(directory)).map(Path::toFile).forEach(directoryFile -> { + if (directoryFile.isDirectory()) { + directories.add(directoryFile); + } else { + try { + SecureDelete.deleteFile(directoryFile, iterations, buffer); + } catch (IOException e) { + e.printStackTrace(); + } + while (directoryFile.exists()) { + if (directoryFile.delete()) { + break; + } + } + } + }); + + File deleteDirectory = directories.last(); + while (deleteDirectory != null) { + deleteDirectory.delete(); + + while (deleteDirectory.delete()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + deleteDirectory = directories.lower(deleteDirectory); + } + } + + /** + *

Overwrites the file {@param iterations} times line by line with random bytes (minimal size {@param minFileSize}; maximal size {@param maxFileSize}) and delete it

+ * + * @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 + * + * @since 1.12.0 + */ + public static void deleteFile(File file, int iterations, byte[] buffer) throws IOException { + SecureRandom secureRandom = new SecureRandom(); + RandomAccessFile raf = new RandomAccessFile(file, "rws"); + for (int i=0; iClass for the user configuration / settings

+ * + * @since 1.12.0 + */ +public class Settings { + + private static double addSettingsGUIX, addSettingsGUIY; + + private static final HashSet protectedSettingsNames = new HashSet<>(Arrays.asList("cryptoGX", "settings")); + + /** + *

Shows a GUI where the user can save settings, which can load later

+ * + * @param rootWindow from which this GUI will get called + * @param userSetting + * @throws IOException + * + * @since 1.11.0 + */ + public static void addSettingGUI(Window rootWindow, Map userSetting) throws IOException { + Map newSettingItems = new HashMap<>(); + + Stage rootStage = new Stage(); + rootStage.initOwner(rootWindow); + Parent addSettingsRoot = FXMLLoader.load(Settings.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() + addSettingsGUIX); + rootStage.setY(event.getScreenY() + addSettingsGUIY); + }); + scene.setOnMousePressed(event -> { + addSettingsGUIX = scene.getX() - event.getSceneX(); + addSettingsGUIY = 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() + addSettingsGUIX); + rootStage.setY(event.getScreenY() + addSettingsGUIY); + }); + menuBar.setOnMousePressed(event -> { + addSettingsGUIX = menuBar.getLayoutX() - event.getSceneX(); + addSettingsGUIY = 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(Utils.algorithms.keySet())); + 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(Utils.algorithms.keySet())); + 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")); + Button fileOutputPathButton = (Button) addSettingsRoot.lookup("#fileOutputPathButton"); + fileOutputPathButton.setOnAction(event -> { + DirectoryChooser directoryChooser = new DirectoryChooser(); + File directory = directoryChooser.showDialog(rootWindow.getScene().getWindow()); + try { + fileOutputPathEntry.setText(directory.getAbsolutePath()); + } catch (NullPointerException e) { + e.printStackTrace(); + } + }); + 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 visiblePasswordEntry = (TextField) addSettingsRoot.lookup("#visiblePasswordEntry"); + CheckBox showPassword = (CheckBox) addSettingsRoot.lookup("#showPassword"); + + showPassword.setOnAction(event -> { + if (showPassword.isSelected()) { + visiblePasswordEntry.setText(hiddenPasswordEntry.getText()); + visiblePasswordEntry.setVisible(true); + hiddenPasswordEntry.setVisible(false); + } else { + hiddenPasswordEntry.setText(visiblePasswordEntry.getText()); + hiddenPasswordEntry.setVisible(true); + visiblePasswordEntry.setVisible(false); + } + }); + CheckBox encryptSettings = (CheckBox) addSettingsRoot.lookup("#encryptSettings"); + encryptSettings.setOnAction(event -> { + if (encryptSettings.isSelected()) { + hiddenPasswordEntry.setDisable(false); + visiblePasswordEntry.setDisable(false); + showPassword.setDisable(false); + } else { + hiddenPasswordEntry.setDisable(true); + visiblePasswordEntry.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 (protectedSettingsNames.contains(settingsNameEntry.getText())) { + warningAlert("Please choose another name for this setting"); + } else if (settingsNameEntry.getText().trim().contains(" ")) { + warningAlert("Setting name must not contain free space"); + } else if (encryptSettings.isSelected()) { + try { + EnDecrypt.AES encrypt; + if (!hiddenPasswordEntry.isDisabled() && !hiddenPasswordEntry.getText().trim().isEmpty()) { + encrypt = new EnDecrypt.AES(hiddenPasswordEntry.getText(), new byte[16]); + } else if (!visiblePasswordEntry.getText().trim().isEmpty()) { + encrypt = new EnDecrypt.AES(visiblePasswordEntry.getText(), new byte[16]); + } else { + throw new InvalidKeyException("The key must not be empty"); + } + + newSettingItems.put("encrypted", "true"); + + 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()))); + + if (!config.isFile()) { + try { + if (!config.createNewFile()) { + warningAlert("Couldn't create config file"); + } else { + addSetting(config, settingsNameEntry.getText().trim(), newSettingItems); + } + } catch (IOException e) { + e.printStackTrace(); + errorAlert("Couldn't create config file", e.getMessage()); + } + } else { + addSetting(config, settingsNameEntry.getText().trim(), newSettingItems); + } + + rootStage.close(); + } catch (InvalidKeyException e) { + warningAlert("The key must not be empty"); + } catch (NoSuchPaddingException | NoSuchAlgorithmException | IllegalBlockSizeException | BadPaddingException | InvalidKeySpecException e) { + e.printStackTrace(); + } + } else { + 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())); + + if (!config.isFile()) { + try { + if (!config.createNewFile()) { + warningAlert("Couldn't create config file"); + } else { + addSetting(config, settingsNameEntry.getText().trim(), newSettingItems); + } + } catch (IOException e) { + e.printStackTrace(); + errorAlert("Couldn't create config file", e.getMessage()); + } + } else { + addSetting(config, settingsNameEntry.getText().trim(), newSettingItems); + } + + rootStage.close(); + } + }); + }); + }); + + thread.start(); + + rootStage.showAndWait(); + } + + /** + *

Shows a GUI where the user can export settings to a extra file

+ * + * @param rootWindow from which this GUI will get called + * @throws IOException + * + * @since 1.11.0 + */ + public static void exportSettingsGUI(Window rootWindow) throws IOException { + Stage rootStage = new Stage(); + rootStage.initOwner(rootWindow); + Parent exportSettingsRoot = FXMLLoader.load(Settings.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() + addSettingsGUIX); + rootStage.setY(event.getScreenY() + addSettingsGUIY); + }); + scene.setOnMousePressed(event -> { + addSettingsGUIX = scene.getX() - event.getSceneX(); + addSettingsGUIY = 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() + addSettingsGUIX); + rootStage.setY(event.getScreenY() + addSettingsGUIY); + }); + menuBar.setOnMousePressed(event -> { + addSettingsGUIX = menuBar.getLayoutX() - event.getSceneX(); + addSettingsGUIY = menuBar.getLayoutY() - event.getSceneY(); + }); + ImageView closeButton = (ImageView) exportSettingsRoot.lookup("#closeButton"); + closeButton.setOnMouseClicked(event -> rootStage.close()); + + VBox settingsBox = (VBox) exportSettingsRoot.lookup("#settingsBox"); + Platform.runLater(() -> readSettings(config).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"), + new FileChooser.ExtensionFilter("All files", "*.*")); + File file = fileChooser.showSaveDialog(exportSettingsRoot.getScene().getWindow()); + if (file != null) { + TreeMap> writeInfos = new TreeMap<>(); + TreeMap> settings = readSettings(config); + for (int i=0; iShows a GUI where the user can load saved settings

+ * + * @param rootWindow from which this GUI will get called + * @return the settings that the user has chosen + * @throws IOException + * + * @since 1.11.0 + */ + public static TreeMap> loadSettingsGUI(Window rootWindow) throws IOException { + Button[] outerLoadButton = new Button[1]; + HashMap setting = new HashMap<>(); + TreeMap> settingItems = readSettings(config); + TreeMap> returnItems = new TreeMap<>(); + + Stage rootStage = new Stage(); + rootStage.initOwner(rootWindow); + AnchorPane loadSettingsRoot = FXMLLoader.load(Settings.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(Settings.class.getResource("resources/cryptoGX.png").toExternalForm())); + Scene scene = new Scene(loadSettingsRoot, 242, 235); + + scene.setOnMouseDragged(event -> { + rootStage.setX(event.getScreenX() + addSettingsGUIX); + rootStage.setY(event.getScreenY() + addSettingsGUIY); + }); + scene.setOnMousePressed(event -> { + addSettingsGUIX = scene.getX() - event.getSceneX(); + addSettingsGUIY = 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() + addSettingsGUIX); + rootStage.setY(event.getScreenY() + addSettingsGUIY); + }); + menuBar.setOnMousePressed(event -> { + addSettingsGUIX = menuBar.getLayoutX() - event.getSceneX(); + addSettingsGUIY = menuBar.getLayoutY() - event.getSceneY(); + }); + + ImageView closeButton = (ImageView) loadSettingsRoot.lookup("#closeButton"); + if (settingItems.isEmpty()) { + rootStage.close(); + } + + closeButton.setOnMouseClicked(event -> { + 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 (!Boolean.parseBoolean(settingItems.firstEntry().getValue().get("encrypted").trim())) { + keyHideEntry.clear(); + keyHideEntry.setDisable(true); + keyShowEntry.setDisable(true); + showPassword.setDisable(true); + } + settingsBox.setOnAction(event -> { + try { + if (!Boolean.parseBoolean(settingItems.get(settingsBox.getSelectionModel().getSelectedItem().toString()).get("encrypted").trim())) { + 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 selectedSetting = settingItems.get(settingName); + if (keyHideEntry.isDisabled() && showPassword.isDisabled() && showPassword.isDisabled()) { + 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 { + Map 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 (InvalidKeyException e) { + warningAlert("Wrong key is given"); + } catch (NoSuchPaddingException | NoSuchAlgorithmException | IllegalBlockSizeException | BadPaddingException | InvalidKeySpecException e) { + e.printStackTrace(); + warningAlert("Wrong key is given or the config wasn't\nsaved correctly"); + } + } + }); + outerLoadButton[0] = loadButton; + + Button deleteButton = (Button) loadSettingsRoot.lookup("#deleteButton"); + deleteButton.setOnAction(event -> { + AtomicReference deleteQuestionX = new AtomicReference<>((double) 0); + AtomicReference 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(Settings.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 result = deleteQuestion.showAndWait(); + if (result.get() == ButtonType.OK) { + if (settingItems.size() - 1 <= 0) { + for (int i = 0; i < 100; i++) { + if (config.isFile()) { + try { + SecureDelete.deleteFile(config, 5, new byte[64]); + isConfig = false; + rootStage.close(); + break; + } catch (IOException e) { + e.printStackTrace(); + } + } + } + rootStage.close(); + } else if (deleteSetting(config, settingsBox.getSelectionModel().getSelectedItem().toString())) { + settingItems.remove(settingsBox.getSelectionModel().getSelectedItem().toString()); + + settingsBox.setItems(FXCollections.observableArrayList(settingItems.keySet())); + settingsBox.setValue(settingItems.firstKey()); + } else { + warningAlert("Couldn't delete setting '" + settingsBox.getSelectionModel().getSelectedItem().toString() + "'"); + } + } + }); + }); + }); + + thread.start(); + + rootStage.setScene(scene); + rootStage.showAndWait(); + + return returnItems; + } + + /** + *

Shows a GUI where the user can save the current settings

+ * + * @param settingName name of the new setting + * @param newSetting is the new setting key value pair + * + * @since 1.12.0 + */ + public static void addSetting(File file, String settingName, Map newSetting) { + TreeMap> settings = readSettings(file); + settings.put(settingName, newSetting); + writeSettings(file, settings); + } + + /** + *

Deletes a saved setting

+ * + * @param settingName of the setting + * @return if the setting could be found + * + * @since 1.12.0 + */ + public static boolean deleteSetting(File file, String settingName) { + StringBuilder newConfig = new StringBuilder(); + boolean delete = false; + boolean found = false; + + try { + BufferedReader configReader = new BufferedReader(new FileReader(file)); + + String line; + + while ((line = configReader.readLine()) != null) { + line = line.trim(); + + if (line.startsWith("[") && line.endsWith("]")) { + if (line.replace("[", "").replace("]", "").split(" ")[0].equals(settingName)) { + delete = true; + found = true; + } else if (delete) { + delete = false; + newConfig.append(line).append("\n"); + } else { + newConfig.append(line).append("\n"); + } + } else if (!delete) { + newConfig.append(line).append("\n"); + } + } + + configReader.close(); + + BufferedWriter configFile = new BufferedWriter(new FileWriter(file)); + configFile.write(newConfig.toString()); + configFile.newLine(); + + configFile.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + return found; + } + + /** + *

Reads all settings saved in a file + * + * @param file from which the settings should be read from + * @return the settings + * + * @since 1.12.0 + */ + public static TreeMap> readSettings(File file) { + TreeMap> returnMap = new TreeMap<>(); + String settingName = null; + Map settingValues = new HashMap<>(); + + try { + BufferedReader configReader = new BufferedReader(new FileReader(file)); + + String line; + + while ((line = configReader.readLine()) != null) { + + if (line.isEmpty()) { + continue; + } else if (line.startsWith("[") && line.endsWith("]")) { + if (settingName != null) { + returnMap.put(settingName, settingValues); + settingValues = new HashMap<>(); + } + String[] newSetting = line.replace("[", "").replace("]", "").split(" "); + settingName = newSetting[0].trim(); + String[] encoded = newSetting[1].split("="); + settingValues.put("encrypted", encoded[1]); + } else { + String[] keyValue = line.split("="); + try { + settingValues.put(keyValue[0], keyValue[1]); + } catch (IndexOutOfBoundsException e) { + settingValues.put(keyValue[0], ""); + } + } + } + + if (settingName != null) { + returnMap.put(settingName, settingValues); + } + + configReader.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + warningAlert("Couldn't find file '" + file.getAbsolutePath() + "'"); // this should never raise + } catch (IOException e) { + e.printStackTrace(); + errorAlert("An IO Exception occurred", e.getMessage()); + } + + return returnMap; + } + + /** + *

Writes settings (could be more than one) to a file

+ * + * @param file where the settings should be written in + * @param settings of the user + * + * @since 1.12.0 + */ + public static void writeSettings(File file, TreeMap> settings) { + try { + BufferedWriter configWriter = new BufferedWriter(new FileWriter(file)); + + for (Map.Entry> settingElement: settings.entrySet()) { + configWriter.write("[" + settingElement.getKey() + " encrypted=" + Boolean.parseBoolean(settingElement.getValue().get("encrypted")) + "]"); + configWriter.newLine(); + for (Map.Entry entry : settingElement.getValue().entrySet()) { + String key = entry.getKey(); + if (!key.equals("encrypted")) { + configWriter.write(entry.getKey() + "=" + entry.getValue()); + configWriter.newLine(); + } + } + } + configWriter.newLine(); + + configWriter.close(); + } catch (IOException e) { + e.printStackTrace(); + errorAlert("An error occurred while saving the settings", e.getMessage()); + } + } + +} diff --git a/src/org/bytedream/cryptogx/Utils.java b/src/org/bytedream/cryptogx/Utils.java new file mode 100644 index 0000000..8b4f8e0 --- /dev/null +++ b/src/org/bytedream/cryptogx/Utils.java @@ -0,0 +1,51 @@ +package org.bytedream.cryptogx; + +import java.util.TreeMap; + +/** + *

Support class

+ * + * @since 1.3.0 + */ +public class Utils { + + public static TreeMap algorithms = allAlgorithms(); + + /** + *

Get all available algorithms

+ * + * @return all available algorithms + * + * @since 1.12.0 + */ + private static TreeMap allAlgorithms() { + TreeMap return_map = new TreeMap<>(); + + int[] aesKeySizes = {128, 192, 256}; + + for (int i: aesKeySizes) { + return_map.put("AES-" + i, "AES"); + } + + return return_map; + } + + /** + *

Checks if any character in {@param characters} appears in {@param string}

+ * + * @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} + * + * @since 1.3.0 + */ + public static boolean hasAnyCharacter(CharSequence characters, String string) { + for (char c: characters.toString().toCharArray()) { + if (string.indexOf(c) != -1) { + return false; + } + } + return true; + } + +} diff --git a/src/org/bytedream/cryptogx/resources/addSettingsGUI.fxml b/src/org/bytedream/cryptogx/resources/addSettingsGUI.fxml new file mode 100644 index 0000000..235a089 --- /dev/null +++ b/src/org/bytedream/cryptogx/resources/addSettingsGUI.fxml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +