diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..5e7e021 --- /dev/null +++ b/.classpath @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4584532 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/bin/ +/lib/ +/src/main/resources/* \ No newline at end of file diff --git a/.project b/.project new file mode 100644 index 0000000..fd458f2 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + FileHasher + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..3a21537 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/README.md b/README.md new file mode 100644 index 0000000..eaef085 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# File-Hasher +A Program for mass-checking the SHA-512 Hash of whole Subdirectories. + +## How to use +Go to the [Releases-Tab](../../releases) and download the attached JAR-File. Put the file into a folder wherever you want but **don't let them in the Downloads Folder** since all Output Files will be created in that folder. + +## Pictures +Here some pictures that you know how the GUI looks like: + +### Program +![Menu on Start](img/menu_start.png)
+![Ready to Go](img/menu_ready.png)
+![Request Done](img/menu_done.png) + +### Icon +![Icon in the Task-Bar](img/icon-taskbar.png) + +## FAQ +- Q: How to use it?
+ A: Read the text above. ;) \ No newline at end of file diff --git a/img/icon-taskbar.png b/img/icon-taskbar.png new file mode 100644 index 0000000..cc0be87 Binary files /dev/null and b/img/icon-taskbar.png differ diff --git a/img/menu_done.png b/img/menu_done.png new file mode 100644 index 0000000..6ac77fb Binary files /dev/null and b/img/menu_done.png differ diff --git a/img/menu_ready.png b/img/menu_ready.png new file mode 100644 index 0000000..d3e61b5 Binary files /dev/null and b/img/menu_ready.png differ diff --git a/img/menu_start.png b/img/menu_start.png new file mode 100644 index 0000000..44e7880 Binary files /dev/null and b/img/menu_start.png differ diff --git a/src/main/java/tk/dmanstrator/filehasher/Hasher.java b/src/main/java/tk/dmanstrator/filehasher/Hasher.java new file mode 100644 index 0000000..55410eb --- /dev/null +++ b/src/main/java/tk/dmanstrator/filehasher/Hasher.java @@ -0,0 +1,175 @@ +package tk.dmanstrator.filehasher; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.commons.io.FileUtils; + +/** + * Generates SHA-512 Hashes. Works for all files of a given Folder or for a given File. + * + * @author DManstrator + * + */ +public class Hasher { + + /** + * Encoding for the File in case a filename or the Directory has Umlauts. + */ + private static final String ENCODING = "UTF-8"; + + /** + * Hashing Method. Creates an Output File and returns the Path to it. + * + * @param mainFolderName + * Folder to hash + * @throws IllegalArgumentException + * When the Path is not a folder + * @throws FileNotFoundException + * When the Output File couldn't be created + * @throws UnsupportedEncodingException + * When the Encoding for the Output File is Unsupported + */ + public String hash(String mainFolderName) throws IllegalArgumentException, FileNotFoundException, UnsupportedEncodingException { + File mainPath = new File(mainFolderName); + if (!mainPath.isDirectory()) { + throw new IllegalArgumentException(String.format("Given Folder '%s' is not a folder, re-check that!", mainFolderName)); + } + + Map filesWithHashes = getHashesOfFiles(mainFolderName); + + String tmpFolderName = getFolderName(mainFolderName); // tmp for using folderName in Stream#map + String folderName = tmpFolderName != null ? tmpFolderName : "null"; + + StringBuilder builder = new StringBuilder("Path to scan: " + mainFolderName + System.lineSeparator()); + String hashes = filesWithHashes.entrySet().stream() + .map(entry -> String.format("%s: %s", entry.getKey().getPath().substring(mainFolderName.length() - folderName.length()), + entry.getValue())) + .collect(Collectors.joining(System.lineSeparator())); + + String output = builder.append(hashes).toString(); + + String dateTime = getDateTime(); + File outputFile = new File(String.format("%s-Hashes_%s.txt", folderName, dateTime)); + try { + PrintWriter writer = new PrintWriter(outputFile.getName(), ENCODING); + writer.write(output); + writer.flush(); + writer.close(); + } catch (FileNotFoundException e) { + throw new FileNotFoundException(String.format("An error occured while creating the output file '%s', " + + "make sure the program has the rights to do so!", outputFile.getAbsolutePath())); + } catch (UnsupportedEncodingException e) { + throw new UnsupportedEncodingException(String.format("An error occured because '%s' is an Unsupported Encoding!", ENCODING)); + } + return outputFile.getAbsolutePath(); + } + + /** + * Computes a SHA-512 Hash for every file of the given folder path. + * + * @param mainFolderName + * Path to the starting Directory + * @return a Map with the File as the Key and the SHA-512 Hash as the Value + */ + public Map getHashesOfFiles(String mainFolderName) { + return getHashesOfFiles(new File(mainFolderName)); + } + + /** + * Computes a SHA-512 Hash for every file of the given folder path. + * + * @param mainFolder + * Path as File to the starting Directory + * @return a Map with the File as the Key and the SHA-512 Hash as the Value + */ + public Map getHashesOfFiles(File mainFolder) { + if (!mainFolder.isDirectory()) { + return new HashMap<>(); + } + Collection listFiles = FileUtils.listFiles(mainFolder, null, true); + return listFiles.stream().collect(Collectors.toMap(p -> p, p -> getHashOfFile(p))); + } + + /** + * Generates a SHA-512 Hash of a File. + * + * @param filename + * Name of the File to get the Hash from + * @return a SHA-512 Hash of given File or null if something + * went wrong + */ + public String getHashOfFile(String filename) { + return getHashOfFile(new File(filename)); + } + + /** + * Generates a SHA-512 Hash of a File. + * + * @param file + * File to get the Hash from + * @return a SHA-512 Hash of given File or null if something + * went wrong + * @see https://stackoverflow.com/a/33085670 + */ + public String getHashOfFile(File file) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-512"); + byte[] digest = md.digest(Files.readAllBytes(Paths.get(file.getAbsolutePath()))); + StringBuilder sb = new StringBuilder(); + for(int i=0; i< digest.length; i++){ + sb.append(Integer.toString((digest[i] & 0xff) + 0x100, 16).substring(1)); + } + return sb.toString(); + } catch (NoSuchAlgorithmException | IOException e) { + return null; + } + } + + /** + * Gets the deepest Folder Name from a given Path. + * + * @param mainFolderName + * Main Folder Name + * @return the deepest Folder Name from a given Path or null if + * no folder could be retrieved from the given Folder Name. + */ + private String getFolderName(String mainFolderName) { + int lastIndex = mainFolderName.lastIndexOf('/'); + if (lastIndex == -1) { // Maybe Windows + int lastIndexWindows = mainFolderName.lastIndexOf('\\'); + if (lastIndexWindows != -1) { + lastIndex = lastIndexWindows; + } else { + return null; // No slashes at all + } + } + return mainFolderName.substring(lastIndex + 1, mainFolderName.length()); + } + + /** + * Gets the current Time in Format yyyy-MM-dd_HH-mm-ss. + * + * @return the current Time as a readable String + */ + private String getDateTime() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(System.currentTimeMillis()); + return new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(cal.getTime()).toString(); + } + +} \ No newline at end of file diff --git a/src/main/java/tk/dmanstrator/filehasher/Main.java b/src/main/java/tk/dmanstrator/filehasher/Main.java new file mode 100644 index 0000000..d349565 --- /dev/null +++ b/src/main/java/tk/dmanstrator/filehasher/Main.java @@ -0,0 +1,210 @@ +package tk.dmanstrator.filehasher; + +import java.awt.Desktop; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import javafx.application.Application; +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.stage.DirectoryChooser; +import javafx.stage.Stage; + +/** + * Main File with GUI Support to execute the File Hasher. + * + * @author DManstrator + * + */ +public class Main extends Application { + + /** + * Starts the GUI. + */ + @Override + public void start(Stage primaryStage) throws Exception { + final DirectoryChooser directoryChooser = new DirectoryChooser(); + configuringDirectoryChooser(directoryChooser); + + // Creating a GridPane container + GridPane grid = new GridPane(); + grid.setVgap(5); + grid.setHgap(5); + + final Label pathLabel = new Label(); + final Label pathNameLabel = new Label("Path:"); + pathNameLabel.setMinWidth(30); + + GridPane.setConstraints(pathNameLabel, 0, 0); + GridPane.setConstraints(pathLabel, 1, 0); + + grid.getChildren().addAll(pathNameLabel, pathLabel); + + Button chooseButton = new Button("Choose A Path"); + Button okayButton = new Button("Okay"); + Button closeButton = new Button("Exit"); + + chooseButton.setOnAction(action -> { + File dir = directoryChooser.showDialog(primaryStage); + if (dir != null) { + pathLabel.setText(dir.getAbsolutePath()); + okayButton.setDisable(false); + } else { + pathLabel.setText(null); + okayButton.setDisable(true); + } + }); + + okayButton.setDisable(true); + + closeButton.setOnAction(action -> { + primaryStage.close(); + }); + + HBox buttons = new HBox(); + buttons.setSpacing(5); + buttons.getChildren().addAll(chooseButton, okayButton, closeButton); + + VBox root = new VBox(); + root.setPadding(new Insets(10)); + root.setSpacing(5); + + root.getChildren().addAll(grid, buttons); + + Scene scene = new Scene(root); + + Image icon = new Image(getClass().getClassLoader().getResourceAsStream("img/logo.png")); + primaryStage.setTitle("File-Hasher"); + primaryStage.setScene(scene); + primaryStage.getIcons().add(icon); + primaryStage.setMinWidth(270); + primaryStage.setMinHeight(115); + primaryStage.show(); + + okayButton.setOnAction(action -> { + Hasher hasher = new Hasher(); + + Button gotoButton = new Button("Open Folder"); + Button returnButton = new Button("Return"); + + String pathToOutputFile = null; + String returnValue; + try { + pathToOutputFile = hasher.hash(pathLabel.getText()); + returnValue = String.format("Successfully created %s as the Output File!", pathToOutputFile); + } catch (IllegalArgumentException | FileNotFoundException | UnsupportedEncodingException e) { + returnValue = e.getMessage(); + gotoButton.setDisable(true); + } + + Label userInformation = new Label(returnValue); + userInformation.setWrapText(true); + + final String outputPath = pathToOutputFile; + gotoButton.setOnAction(gotoAction -> { + String error = openFolderWithFile(outputPath); + if (error != null) { + userInformation.setText(userInformation.getText() + System.lineSeparator() + error); + gotoButton.setDisable(true); + } + }); + + returnButton.setOnAction(returnAction -> { + buttons.getChildren().add(closeButton); // Disappears if not re-added + pathLabel.setText(null); + okayButton.setDisable(true); + primaryStage.setScene(scene); + }); + + HBox newButtons = new HBox(); + newButtons.setSpacing(5); + newButtons.getChildren().addAll(gotoButton, returnButton, closeButton); + + VBox out = new VBox(); + out.setPadding(new Insets(10)); + out.setSpacing(5); + out.getChildren().addAll(userInformation, newButtons); + out.setMinSize(500, 250); + + Scene infoScreen = new Scene(out); + primaryStage.setScene(infoScreen); + primaryStage.setHeight(100); // trick to use min width + + System.out.println(returnValue); + }); + } + + /** + * Opens the Folder to the given File. + * + * @param outputFile + * File to get Folder from + * @return null if the call was successfully, else an Error Response + */ + private static String openFolderWithFile(String outputFile) { + File testFile = new File(outputFile); + if (!testFile.isFile()) { + return "The file isn't avaliable (anymore)!"; + } + try { + // new ProcessBuilder("explorer.exe", "/select,\"" + outputPath + "\"").start(); // doesn't work + Runtime.getRuntime().exec("explorer.exe /select," + outputFile); + } catch (IOException e) { + String currentDir = System.getProperty("user.dir"); + try { + Desktop.getDesktop().open(new File(currentDir)); + } catch (IOException e1) { + return "An Error occured: Cannot open the Folder for you!"; + } + } + + return null; + } + + /** + * Configures a Title and an initial Directory for a given Directory Chooser. + * + * @param directoryChooser + * Directory Chooser to change things on + */ + private void configuringDirectoryChooser(DirectoryChooser directoryChooser) { + directoryChooser.setTitle("Choose a Directory to Scan"); + directoryChooser.setInitialDirectory(new File(System.getProperty("user.home"))); + } + + /** + * Main Method. Checks for Program Arguments, if none given the GUI will be started. + * + * @param args + * Program Arguments + */ + public static void main(String[] args) { + if (args.length != 0) { + Hasher hasher = new Hasher(); + try { + String pathToOutputFile = hasher.hash(args[0]); + String output = String.format("Successfully created %s as the Output File!", pathToOutputFile); + System.out.println(output); + String error = openFolderWithFile(pathToOutputFile); + if (error != null) { + System.err.println(error); + } + System.exit(0); + } catch (IllegalArgumentException | FileNotFoundException | UnsupportedEncodingException e) { + System.err.println(e.getMessage()); + System.exit(-1); + } + } + + Application.launch(args); + } + +} \ No newline at end of file diff --git a/src/main/java/tk/dmanstrator/filehasher/package-info.java b/src/main/java/tk/dmanstrator/filehasher/package-info.java new file mode 100644 index 0000000..cfc3c99 --- /dev/null +++ b/src/main/java/tk/dmanstrator/filehasher/package-info.java @@ -0,0 +1,7 @@ +/** + * Main Package. + * + * @author DManstrator + * + */ +package tk.dmanstrator.filehasher; \ No newline at end of file diff --git a/src/test/java/tk/dmanstrator/filehasher/HasherTest.java b/src/test/java/tk/dmanstrator/filehasher/HasherTest.java new file mode 100644 index 0000000..075ff8c --- /dev/null +++ b/src/test/java/tk/dmanstrator/filehasher/HasherTest.java @@ -0,0 +1,140 @@ +package tk.dmanstrator.filehasher; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; + +import tk.dmanstrator.filehasher.Hasher; + +/** + * Test Class for testing the Hasher. Used the following websites to compare the Hashes:
+ * https://emn178.github.io/online-tools/sha512_file_hash.html
+ * https://hash.online-convert.com/sha512-generator + * + * @author DManstrator + * + */ +public class HasherTest { + + /** + * Map with Filepaths and SHA-512 Hashes to compare. + */ + private final static Map HASHES = new HashMap() { + private static final long serialVersionUID = 5636217836120806223L; + { + put("src\\test\\resources\\folder1\\Testfile.txt", + "861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8"); + put("src\\test\\resources\\folder1\\Another-Testfile.txt", + "f11a72665f07e477918c6c8cef29f30942ea7d4ac7dd15bb61b0f6d52e284c4b31978d3238878994a30b7ddcf22cb4ffdad82cb407ecd418a92d0ce78c1a49dc"); + put("src\\test\\resources\\folder2\\Test File.txt", + "861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8"); + put("src\\test\\resources\\folder2\\Testfile with Ümläuts.txt", + "c966988e7254a05ae05bb921a2955899dc47c209120b26faf96a6701cd884b258a3eb6bd5c035ed4e3e3865a84719332a05c458dad2da655a46ca29fa6d2dbc9"); + } + }; + + /** + * Tests if the IllegalArgumentException is successfully thrown. + * + * @throws IllegalArgumentException + * @throws FileNotFoundException + * @throws UnsupportedEncodingException + */ + @Test(expected = IllegalArgumentException.class) + public void testHashingFoldersNotValidPath() throws IllegalArgumentException, FileNotFoundException, UnsupportedEncodingException { + Hasher hasher = new Hasher(); + hasher.hash("not/valid/path"); + } + + /** + * Tests if the program works normally and creates an output file. Also used "Windows Slashes" (Backslashes). + * + * @throws IllegalArgumentException + * @throws FileNotFoundException + * @throws UnsupportedEncodingException + */ + @Test + public void testHashingFoldersRun() throws IllegalArgumentException, FileNotFoundException, UnsupportedEncodingException { + Hasher hasher = new Hasher(); + String hash = hasher.hash("src\\test\\resources\\folder1"); + Assert.assertNotNull(hash); + } + + /** + * Tests if the SHA-512 Hashes are successfully computed. + */ + @Test + public void testHashingFoldersNormal() { + Hasher hasher = new Hasher(); + Map hashesOfFiles = hasher.getHashesOfFiles("src/test/resources/folder1"); + for (Map.Entry entry : hashesOfFiles.entrySet()) { + boolean found = false; + for (Map.Entry hashEntry : HASHES.entrySet()) { + if (entry.getKey().equals(new File(hashEntry.getKey()))) { + found = true; + Assert.assertEquals(entry.getValue(), hashEntry.getValue()); + break; + } + } + Assert.assertTrue(found); + } + } + + /** + * Tests if the SHA-512 Hashes are successfully computed even there are Umlauts in the File Names. + */ + @Test + public void testHashingFoldersAdvanced() { + Hasher hasher = new Hasher(); + Map hashesOfFiles = hasher.getHashesOfFiles("src/test/resources/folder2"); + for (Map.Entry entry : hashesOfFiles.entrySet()) { + boolean found = false; + for (Map.Entry hashEntry : HASHES.entrySet()) { + if (entry.getKey().equals(new File(hashEntry.getKey()))) { + found = true; + Assert.assertEquals(entry.getValue(), hashEntry.getValue()); + break; + } + } + Assert.assertTrue(found); + } + } + + /** + * Checks if the hash of a normal file gets successfully computed. + */ + @Test + public void testHashingFileNormal() { + Hasher hasher = new Hasher(); + String hashOfFile = hasher.getHashOfFile("src/test/resources/folder1/Testfile.txt"); + Assert.assertEquals("861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8", + hashOfFile); + } + + /** + * Checks if the hash of a file with Umlauts in the name gets successfully computed. + */ + @Test + public void testHashingFileAdvanced() { + Hasher hasher = new Hasher(); + String hashOfFile = hasher.getHashOfFile("src/test/resources/folder2/Testfile with Ümläuts.txt"); + Assert.assertEquals("c966988e7254a05ae05bb921a2955899dc47c209120b26faf96a6701cd884b258a3eb6bd5c035ed4e3e3865a84719332a05c458dad2da655a46ca29fa6d2dbc9", + hashOfFile); + } + + /** + * Checks if an invalid folder path returns an empty map. + */ + @Test + public void testEmptyMap() { + Hasher hasher = new Hasher(); + Map hashesOfFiles = hasher.getHashesOfFiles("not/valid/path"); + Assert.assertTrue(hashesOfFiles.isEmpty()); + } + +} \ No newline at end of file diff --git a/src/test/resources/folder1/Another-Testfile.txt b/src/test/resources/folder1/Another-Testfile.txt new file mode 100644 index 0000000..32f411c --- /dev/null +++ b/src/test/resources/folder1/Another-Testfile.txt @@ -0,0 +1 @@ +Another one! \ No newline at end of file diff --git a/src/test/resources/folder1/Testfile.txt b/src/test/resources/folder1/Testfile.txt new file mode 100644 index 0000000..c57eff5 --- /dev/null +++ b/src/test/resources/folder1/Testfile.txt @@ -0,0 +1 @@ +Hello World! \ No newline at end of file diff --git a/src/test/resources/folder2/Test File.txt b/src/test/resources/folder2/Test File.txt new file mode 100644 index 0000000..c57eff5 --- /dev/null +++ b/src/test/resources/folder2/Test File.txt @@ -0,0 +1 @@ +Hello World! \ No newline at end of file diff --git "a/src/test/resources/folder2/Testfile with \303\234ml\303\244uts.txt" "b/src/test/resources/folder2/Testfile with \303\234ml\303\244uts.txt" new file mode 100644 index 0000000..8f80d0c --- /dev/null +++ "b/src/test/resources/folder2/Testfile with \303\234ml\303\244uts.txt" @@ -0,0 +1 @@ +Just in case.. \ No newline at end of file