I wrote this Java code for fun to benchmark the running process with and without displaying the current progress. This algorithm exhibits the performance drawback when the status displaying is actively refreshing in the command line.
What is this Java code doing?
For testing Java’s PrintStream(System.out) performance I decided to use MD5 hash calculation. This script is nothing fancy, it runs from command line with two arguments supplied to it, which are simple text files (path to the files). The first file contains the MD5 hash – or multiple hashes separated with carriage returns – stored as a text file. The following file is the password list in readable format also separated with carriage returns. The process will load one password and calculate its MD5 hash which then matched with the other one. If it fails – I’m assured that it is going to – then it tries the next password and so on.
When a password fails a number displays in the command line, which increments with the number of failed tries. Time is measured in milliseconds, the starting value is set right before the loop and the ending when the loop is finished. The difference between the two values is the elapsed time. The process runs twice, without and with the status counter. After the two cycles are finished the code exits leaving the two test results in the console.
The Java source code
The code can be viewed here or downloaded from along with the sample hash and password files. There is only one hash in the sample file and 1 million passwords in the other one.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
import java.io.PrintStream; import java.io.FileInputStream; import java.io.IOException; import java.util.Scanner; import java.security.MessageDigest; import javax.xml.bind.DatatypeConverter; /** * * @author gellai.com */ class JavaBenchmark { private static JavaBenchmark hash, password; private static final PrintStream PS = new PrintStream(System.out); private FileInputStream inputStream; private Scanner scanner; private static MessageDigest md; private int testNumber, modulus; private String hashString; /** * * @param file */ public JavaBenchmark(String file) { try { inputStream = new FileInputStream(file); scanner = new Scanner(inputStream); } catch (IOException e) { PS.println(e); System.exit(1); } try { md = MessageDigest.getInstance("MD5"); } catch (Exception e) { PS.println(e); System.exit(2); } testNumber = 0; modulus = 0; } /** * */ private void validateHash() { if (!hash.hashString.matches("^[a-zA-Z0-9]*$") || hash.hashString.length() != 32) { PS.println("\nInvalid hash!"); System.exit(3); } } /** * * @param str * @return */ private String getMD5Hash(String str) { String md5; md.update(str.getBytes()); byte[] digest = md.digest(); md5 = DatatypeConverter.printHexBinary(digest); return md5; } /** * */ private void hashRun() { long startTimeMs, endTimeMs, elapsedTimeMs; int i = 1; hash.printTestDetails(); startTimeMs = System.currentTimeMillis(); /** * START OF CALCULATION PROCESS */ while (password.scanner.hasNextLine()) { String passwordLine = password.scanner.nextLine(); String md5Hash = password.getMD5Hash(passwordLine); if( (password.modulus > 0) && (i % password.modulus == 0) ) { PS.print(" Tried hashes per " + password.modulus + ": " + i + "\r"); } if (md5Hash.equalsIgnoreCase(hash.hashString)) { PS.println(" **Password found: " + passwordLine); break; } i++; } /** * END OF CALCULATION PROCESS */ endTimeMs = System.currentTimeMillis(); elapsedTimeMs = endTimeMs - startTimeMs; if( (password.modulus == 0) || (i % password.modulus != 0) ) { i--; for(int s=0; s<50; s++) { PS.print(" "); } PS.print("\r"); PS.println(" Total passwords: " + i); } showElapsedTime(elapsedTimeMs); } /** * * @param elapsedMs */ private void showElapsedTime(long elapsedMs) { double elapsedS = elapsedMs / 1000.0; PS.println(" Elapsed time: " + elapsedS + " seconds"); PS.println("----------------------------------------"); } /** * */ private void printTestDetails() { hash.testNumber++; PS.println("\nTest: " + hash.testNumber); PS.println(" Status displaying steps: " + password.modulus); PS.println(" Hash: " + hash.hashString); } /** * * @param args */ public static void main(String[] args) { if (args.length != 2 || args[0] == null || args[1] == null) { PS.print("\nMissing arguments!\n\nUsage:\nJavaBenchmark {Hash File} {Password File}\n"); System.exit(4); } /** * Checking if the hash file is valid */ hash = new JavaBenchmark(args[0]); hash.hashString = hash.scanner.nextLine(); hash.validateHash(); /** * The first test is not displaying * the status of the progress. */ password = new JavaBenchmark(args[1]); password.modulus = 0; password.hashRun(); /** * The second run displays the progress * status. */ password = new JavaBenchmark(args[1]); password.modulus = 1; password.hashRun(); System.exit(0); } } |
For outputting the process counter I used System.out
which declared as an instance of PrintStream
class. This is the main function which can dramatically slow down the whole process.
Password files can be very large, they could take even 85Gb. Reading them into the memory is a very bad practice. For that reason FileStream
class is an acceptable solution. It streams the file byte-by-byte making the application efficient and suitable for larger files.
If you want to increase the counter step just change the value of password.modulus
property in the main()
function. In my first cycle it is set to password.modulus = 0
to not to display the progress counter. The second cycle has a setting password.modulus = 1
to show every failed tries. Setting it password.modulus = 1000
will refresh the counter after every 1000th failed tries therefore the process will be faster.
Compiling and usage
After installing Java Development Kit, compiling the source code is very simple.
1 |
$ javac JavaBenchmark.java |
To be able to run the application:
1 |
$ java JavaBenchmark hash.txt passwords.txt |
Result of the benchmark
After running the following command I had the following results.
Test 1 shows that, trying 1,000,000 passwords against a MD5 hash and not showing the counter after every failed tries, would take only 1.386 seconds. That is around 721,500 passwords/second. The Total passwords started at 0 and jumped to 1000000 after the task finished. In Test 2, the counter number was incrementally displayed after every failed tries by PrintStream(System.out). The process took 26.507 seconds which is around 37,725 passwords/seconds. This is 94.7713% slower than Test 1!!!